4

私はこの種のコードを持っています:

class Ref {<undefined>};
Ref refObjectForA, refObjectForB;

class Base
{
  public:
    Base(const Ref & iRef) : _ref(iRef) {}
    virtual ~Base() {}

    const Ref & ref;
};

class A: public Base
{
  public:
     A() : Base(refObjectForA) {}
     virtual ~A() {}
};

class B: public A
{
  public:
    B() : Base(refObjectForB) {} // won't compile: Base is not direct base of B
    virtual ~B() {}
};

属性は参照なので、コンストラクタでしか設定できないと思うので、 でコンストラクタを呼び出す必要がありBaseますB()。私は 2 つの方法を見つけました: 「フォワード」コンストラクターを提供しますA(ただし、これは、継承される可能性のあるすべてのクラスにコードを追加することを意味します)。

A(const Ref& iRef): Base(iRef)

または仮想継承を使用します。

class A: public virtual Base

2 番目のオプションでは、実装でより簡単なコードを使用できますが、B仮想継承を醜いトリックで誤用しているのか、それとも有効なユースケースなのか疑問に思っています。

  • この場合、仮想継承を使用できますか?
  • いいえの場合、どのような理由で?

私が見つけた「予期しない」動作の 1 つは、仮想継承のためにポインターへのポインターを作成static_castできないことです。BaseB

さらに、なぜそれが機能するのかも疑問に思っています(つまり、なぜ a B().ref == refObjectForB):デフォルトA()コンストラクターへの暗黙的な呼び出しは、明示的なコンストラクターの後に属性B()を上書きすると思いますが、仮想継承ではそうではないかもしれません。refBase

4

3 に答える 3

4

継承階層に固執したい場合に私が見ることができる最良のオプションは、Baseクラスに転送される参照を取得する保護されたコンストラクターを実装することです。コンストラクターを保護すると、このコンストラクターを使用して (最終的な) インスタンスを構築できないことが保証されるため、スーパークラスを初期化するためにサブクラスでのみ使用されます。

多かれ少なかれ醜くて危険なマクロを使うと、これは簡単に書けるようになります:

#define REF_FORWARD_CTOR(ClassName, DirectSuperClassName) \
    protected: ClassName(class Ref &r) : DirectSuperClassName(r) {} \
    public:

class A : public Base
{
    REF_FORWARD_CTOR(A, Base)
public:
    A() : Base(refObjectForA) {} // normal ctor
};

class B : public A
{
    REF_FORWARD_CTOR(B, A)
public:
    B() : A(refObjectForB) {} // normal ctor
};

別の設計は、 letABboth が から (直接) 派生することBaseです。次に、複数の継承と「共通クラス」を使用して機能を追加します。目的に応じて、おそらくプライベートです。

class Base {
};

class Common {
    // common stuff used by both A and B
};

class A : public Base, public Common {
    // no further stuff here
};

class B : public Base, public Common {
    // add more stuff, or put it in a common super-class again,
    // if some classes want to inherit from B again
};

この設計の問題点は、 の機能がおよびCommonのものにアクセスできないことです。これを解決するには、次のいずれかを実行します。AB

  • 静的なものだけが必要な場合:具体的な型で/を指定するためにCRTPを使用します: を使用できますが、具体的なインスタンスとは何の関係もありませんABCommonCommon<A>A::...A
  • インスタンスが必要な場合: のコンストラクターでポインター/参照を提供しますCommon(わずかなオーバーヘッド)
  • 最初の 2 つのソリューションを組み合わせる: CRTP を使用し、ラッパー関数を実装し、A関数Bを呼び出して、(または追加のパラメーターを介して)Common<A>Common<B>提供します。thisA*B*
  • 上記と同じですが、Commonこのポインター引数でこれらの関数をオーバーロード/テンプレート化する場合は、クラスを非テンプレート (CRTP なし) にすることもできます (そのように呼び出したい場合は、「関数の CRTP」)。コードは言葉よりも雄弁です。(サンプルコードには参照がなく、「共通クラス」に焦点を当てています。)
于 2013-02-19T14:34:15.147 に答える
3

はい、技術的には仮想継承を使用して、最も派生したクラスで参照を提供するという目標を達成できます。

はい、それはデザインの匂いです。

クラスは、直接のベース以外を認識する必要はありません (仮想継承は、他の理由で必要な場合の例外です)。

于 2013-02-19T14:34:50.103 に答える