ポインターの多型とベクターのC ++問題
-
20-09-2019 - |
質問
次の例を考えてみましょう。
class Foo
{
};
class Bar : public Foo
{
};
class FooCollection
{
protected:
vector<shared_ptr<Foo> > d_foos;
};
class BarCollection : public FooCollection
{
public:
vector<shared_ptr<Bar> > &getBars()
{
// return d_foos won't do here...
}
};
現在のプロジェクトではこのような問題があります。クライアントコードが使用します BarCollection
, 、ポインターを保存します Bars
の d_foos
で宣言されています FooCollection
. 。ポインターのコレクションをクライアントコードにバーにさらしたいと思います。ポインターのベクトルへのクライアントコードアクセスを次のようにすることができます Foo
sとこれらをポインターにキャストします Bar
クライアントコードではsですが、クライアントが知る必要がないので、これは間違っていると感じます Foo
の存在。
aを定義することもできます get()
からオブジェクトを取得するメンバー d_foos
そしてそれらをキャストしますが、これは非常に不器用に感じます。できれば、D_FOOSを次のように返したいだけです vector<shared_ptr<Bar> > &
, 、しかし、私はこれをすることができないようです。
また、私のデザインがまったく間違っているだけかもしれません。しかし、それは最も自然な解決策のように思えました Bar
の専門化です Foo
と BarCollection
の専門化です FooCollection
そして、彼らは機能を共有します。
実装する素晴らしいソリューションを提案していただけますか getBars
の BarCollection
またはより良いデザインの代替品?
編集:
私のデザインは確かに悪かったことがわかりました。 Foocollectionの機能をすべて必要としているにもかかわらず、BarcollectionはFoocollectionではありません。以下の回答に基づいた私の現在のソリューション - これはかなりきれいです - は今です:
class Foo
{
};
class Bar : public Foo
{
};
template<class T>
class Collection
{
vector<shared_ptr<T> > d_items;
};
typedef Collection<Foo> FooCollection;
class BarCollection : public Collection<Bar>
{
// Additional stuff here.
};
すべての優れた提案と例をありがとう!
解決
template<class T>
class MyContainer {
vector<shared_ptr<T> > d_foos;
public:
vector<shared_ptr<T> > & getVector();
};
class FooCollection : public MyContainer<Foo> {
};
class BarCollection : public MyContainer<Bar> {
};
他のヒント
メンバーコンテナの代わりに、コンテナクラスからイテレーターを公開することをお勧めします。そうすれば、コンテナの種類が何であるかは関係ありません。
問題は、あなたがうまくいかない方法で、2つの異なる、かなり自立した種類の多型を混ぜ合わせようとしていることです。テンプレートのコンパイル時間、タイプセーフの多型により、派生タイプのベースタイプを置き換えることはできません。 C ++のテンプレートシステムは、
class<Foo>
と
class<Bar>
1つの提案は、正しいクラスにキャストされるFoo派生アダプターを作成することです。
template <class derived, class base>
class DowncastContainerAdapter
{
private:
std::vector< boost::shared_ptr<base> >::iterator curr;
std::vector< boost::shared_ptr<base> >::const_iterator end;
public:
DowncastContainerAdapter(/*setup curr & end iterators*/)
{
// assert derived actually is derived from base
}
boost::shared_ptr<derived> GetNext()
{
// increment iterator
++curr;
return dynamic_cast<base>(*curr);
}
bool IsEnd()
{
return (curr == end);
}
};
このクラスは、イテレーターと同じ問題を抱えていることに注意してください。ベクトル上の操作はこのクラスを無効にする可能性があります。
別の考え
気付かないかもしれませんが、fooのベクトルを返すだけでまったく問題ないかもしれません。 bar.hを含めることにより、bar.hを介してfoo.hを取得する必要があるため、barのユーザーは既にFooの完全な知識を持っている必要があります。 BarがFooから継承するためには、Foo.hを介してクラスの完全な知識が必要であるためです。上記のソリューションを使用するのではなく、可能であれば、Foo(またはFooのスーパークラス)をインターフェイスクラスにし、そのインターフェイスクラスにポインターのベクトルを渡すことをお勧めします。これはかなり一般的に見られるパターンであり、私が思いついたこの不安定な解決策が眉を上げることはありません:)。それからもう一度あなたはあなたの理由を持っているかもしれません。どちらにしても頑張ってください。
問題は、なぜあなたはそれをするのですか?ユーザーにバーへのポインターのコレクションを提供する場合、あなたはその中にバーだけがあるので、Fooのコレクションにポインターを内部的に保存することは意味がありません。 FooへのポインターのコレクションにFooの異なるサブタイプを保存すると、バーへのポインターのコレクションとして返すことはできません。最初のケースでは、上記のようにテンプレートアプローチを使用する必要があります。そうでなければ、あなたが本当に欲しいものを再考する必要があります。
これをどちらのfoo / barでテンプレート化されたコレクションに置き換えることはできませんか?
class Collection<T> {
protected:
vector<shared_ptr<T> > d_foos;
};
typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
特別なニーズがありますか? BarCollection
に由来する FooCollection
?一般的にa BarCollection
ではありません a FooCollection
, 、通常、でできることの多く FooCollection
で行うべきではありません BarCollection
. 。例えば:
BarCollection *bc = new BarCollection();
FooCollection *fc = bc; // They are derived from each other to be able to do this
fc->addFoo(Foo()); // Of course we can add a Foo to a FooCollection
今、私たちはaを追加しました Foo
想定されるものに反対します BarCollection
. 。場合 BarCollection
この新しく追加された要素にアクセスしようとし、それが Bar
, 、あらゆる種類の醜いことが起こります。
したがって、通常、これを避け、お互いからコレクションクラスを導き出さないようにします。参照してください 質問 約 キャスト容器 このトピックに関するより多くの回答のための派生タイプの...
まず第一に、話しましょう shared_ptr
. 。あなたは知っていますか: boost::detail::dynamic_cast_tag
?
shared_ptr<Foo> fooPtr(new Bar());
shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());
これは非常に便利な方法です。カバーの下で、それはただ実行します dynamic_cast
, 、そこには派手なものはありませんが、簡単な表記法。契約は古典的なものと同じです:もしオブジェクトが実際に指摘していない場合 Bar
(またはそれから派生した)、ヌルポインターが取得されます。
あなたの質問に戻る:悪いコード。
BarCollection
ではありません FooCollection
, 、前述のように、あなたはポインターのベクトルに他の要素を導入できるので、あなたは困っています Bar
ワンズ。
私はそれを拡張しません。なぜなら、これは手元の問題を超えているからであり、私たち(答えようとしているように)はそれから自分自身を抑えるべきだと思います。
参照を渡すことはできませんが、渡すことができます View
.
基本的に、a View
aとして機能する新しいオブジェクトです Proxy
古いものに。使用するのは比較的簡単です boost.iterators 例から。
class VectorView
{
typedef std::vector< std::shared_ptr<Foo> > base_type;
public:
typedef Bar value_type;
// all the cluttering
class iterator: boost::iterator::iterator_adaptor<
iterator,
typename base_type::iterator,
std::shared_ptr<Bar>
>
{
typename iterator_adaptor::reference dereference() const
{
// If you have a heart weakness, you'd better stop here...
return reinterpret_cast< std::shared_ptr<Bar> >(this->base_reference());
}
};
// idem for const_iterator
// On to the method forwarding
iterator begin() { return iterator(m_reference.begin()); }
private:
base_type& m_reference;
}; // class VectorView
ここでの本当の問題はもちろんです reference
少し。取得 NEW
shared_ptr
オブジェクトは簡単で、実行できます dynamic_cast
要求に応じ。取得 reference
に ORIGINAL
shared_ptr
しかし、必要なタイプとして解釈される...実際にはコードで見たいものではありません。
ノート:
boost.fusionを使用してそれよりもうまくやる方法があるかもしれません transform_view クラス、しかし私はそれを理解することができませんでした。
特に、使用 transform_view
私は得ることができます shared_ptr<Bar>
しかし、私は得ることができません shared_ptr<Bar>&
私が繰り返しを拒否したとき、それは根本への参照を返す唯一の使用を考えると迷惑です vector
(そしてではありません const_reference
)は、実際にの構造を変更することです vector
そして、それに含まれるオブジェクト。
注2:
リファクタリングを検討してください。そこには素晴らしい提案がありました。