3

次のコード例を検討してください。

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...
    }
};

私は現在のプロジェクトでこのような問題を抱えています。クライアント コードは、 inで宣言されているin へのBarCollectionポインタを格納する を使用します。ここで、バーへのポインターのコレクションをクライアント コードに公開したいと思います。クライアント コードに s へのポインタのベクトルへのアクセスを許可し、これらをクライアント コード内の s へのポインタにキャストすることもできますが、クライアントは の存在を知る必要がないため、これは間違っていると感じます。Barsd_foosFooCollectionFooBarFoo

get()オブジェクトを取得してキャストするメンバーを定義することもできますd_foosが、これはかなりぎこちなく感じます。できれば、 d_foos を として返したいのvector<shared_ptr<Bar> > &ですが、これができないようです。

また、私の設計が単に間違っている可能性もあります。Barただし、の専門化でFooありBarCollection、の専門化でFooCollectionあり、機能を共有しているため、最も自然な解決策のように見えました。

実装するための優れたソリューション、またはより優れた設計の代替案を提案できますgetBarsBarCollection?

編集:

私のデザインは確かに悪かったことがわかりました。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.
};

すべての優れた提案と例をありがとう!

4

7 に答える 7

3

問題は、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 のスーパー クラス) をインターフェイス クラスにして、そのインターフェイス クラスへのポインターのベクトルを渡すことをお勧めします。これはかなり一般的に見られるパターンであり、私が思いついたこの不安定な解決策に眉をひそめることはありません :)。それからまたあなたの理由があるかもしれません。いずれにせよ頑張ってください。

于 2009-11-16T12:05:25.187 に答える
3

メンバー コンテナーではなく、コンテナー クラスから反復子を公開することをお勧めします。そうすれば、コンテナの種類は関係ありません。

于 2009-11-16T12:08:54.017 に答える
2
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> {
};
于 2009-11-16T12:01:15.303 に答える
2

問題は、なぜそれをするのかということです。ユーザーに Bar へのポインターのコレクションを与える場合、その中には Bar しかないと想定するため、Foo へのポインターをコレクションに内部的に格納しても意味がありません。Foo へのポインターのコレクションに Foo のさまざまなサブタイプを格納する場合、そこにあるすべてのオブジェクトが Bar であるとは限らないため、それを Bar へのポインターのコレクションとして返すことはできません。最初のケース (バーしかないことはわかっています) では、上記のようにテンプレート化されたアプローチを使用する必要があります。それ以外の場合は、自分が本当に望んでいることを再考する必要があります。

于 2009-11-16T12:21:07.850 に答える
1

まず、について話しましょう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(またはそれから派生していない)場合、nullポインターを取得します。

あなたの質問に戻ってください:悪いコード。

BarCollectionはではありません。FooCollection前述のように、ポインタのベクトルに他の要素を導入する可能性があるため、問題が発生しBarます。

これは当面の問題を超えているので、私はそれについては詳しく説明しません。私たちは(答えようとしている人として)それを控えるべきだと思います。

参照を渡すことはできませんが、を渡すことはできますView

基本的に、aは古いViewオブジェクトに対してとして機能する新しいオブジェクトです。例のBoost.IteratorsProxyを使用するのは比較的簡単です。

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必要に応じて実行できます。に到達するreferenceORIGINAL shared_ptr、必要なタイプとして解釈される...実際には私がコードで見たいものではありません。

: Boost.Fusion transform_viewクラス
を使用するよりも優れた方法があるかもしれませんが、私はそれを理解できませんでした。

特に、transform_viewIを使用すると取得できますが、イテレータを逆参照するとshared_ptr<Bar>取得できません。これは、基になる(ではなく)への参照を返す唯一の使用法が、との構造を実際に変更することであることを考えると、煩わしいことです。含まれているオブジェクト。shared_ptr<Bar>&vectorconst_referencevector

注2
リファクタリングを検討してください。そこには素晴らしい提案がありました。

于 2009-11-16T13:01:49.390 に答える
1

これをFoo / Barのいずれかでテンプレート化されたコレクションに置き換えることはできませんか?、このようなもの

class Collection<T> {
protected:
    vector<shared_ptr<T> > d_foos;
};

typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;
于 2009-11-16T11:57:16.137 に答える
1

BarCollectionから派生した特別な必要がありFooCollectionますか? 一般に aBarCollection は ではないため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

Fooこれで、 であるはずのオブジェクトにオブジェクトが追加されましたBarCollection。がこのBarCollection新しく追加された要素にアクセスしようとして、それが であることを期待するとBar、あらゆる種類の醜いことが起こります。

したがって、通常はこれを避け、コレクション クラスを相互に派生させないようにします。このトピックに関するその他の回答については、派生型のコンテナーのキャストに関する質問も参照してください...

于 2009-11-16T12:31:00.563 に答える