20

次のコードでは、クラス C は仮想継承のために必要な A のコンストラクターにアクセスできないようです。それでも、コードはコンパイルおよび実行されます。なぜそれが機能するのですか?

class A {};
class B: private virtual A {};
class C: public B {};

int main() {
    C c;
    return 0;
}

さらに、Aからデフォルトのコンストラクターを削除すると、たとえば

class A {
public:
    A(int) {}
};
class B: private virtual A {
public:
    B() : A(3) {}
};

それから

class C: public B {};

(予期せず)コンパイルされますが、

class C: public B {
public:
    C() {}
};

予想どおり、コンパイルされません。

「g++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)」でコンパイルしたコードですが、他のコンパイラでも同様に動作することが確認されています。

4

3 に答える 3

14

C++ Core Issue #7によると、仮想プライベート ベースを持つクラスは派生できません。これはコンパイラのバグです。

于 2010-03-03T12:52:09.337 に答える
6

2 番目の質問については、暗黙的に定義されていないことが原因である可能性があります。コンストラクターが単に暗黙的に宣言されている場合、エラーは発生しません。例:

struct A { A(int); };
struct B : A { };
// goes fine up to here

// not anymore: default constructor now is implicitly defined 
// (because it's used)
B b;

最初の質問については、コンパイラが使用する名前によって異なります。標準が何を指定しているのかわかりませんが、(継承されたクラス名ではなく) 外部クラス名にアクセスできるため、たとえばこのコードは正しいです。

class A {};
class B: private virtual A {};
class C: public B { C(): ::A() { } }; // don't use B::A

たぶん、この時点で規格は十分に規定されていません。私たちは見なければならないでしょう。


コードに問題はないようです。さらに、コードが有効であるという表示があります。(仮想) 基本クラス サブオブジェクトはデフォルトで初期化されています。クラス名の名前検索が のスコープ内で行われることを意味するテキストはありませんC。規格の内容は次のとおりです。

12.6.2/8(C++0x)

特定の非静的データ メンバーまたは基本クラスが mem-initializer-id によって名前が付けられておらず (コンストラクターに ctor-initializer がないために mem-initializer-list がない場合を含む)、エンティティが仮想でない場合抽象クラスの基底クラス

[...]それ以外の場合、エンティティはデフォルトで初期化されます

また、C++03 にも同様のテキストがあります (あまり明確なテキストではありません。デフォルトのコンストラクターが 1 つの場所で呼び出され、別の場所ではクラスが POD であるかどうかに依存するようになっているだけです)。コンパイラがサブオブジェクトをデフォルトで初期化するには、デフォルトのコンストラクタを呼び出すだけで済みます。最初に基底クラスの名前を検索する必要はありません (どの基底が考慮されるかは 既にわかっています)。

確かに有効であることを意図しているこのコードを考えてみてください。ただし、これを行うと失敗します ( 12.6.2/4C++0x を参照)。

struct A { };
struct B : virtual A { };
struct C : B, A { };
C c;

Aコンパイラのデフォルト コンストラクターが内のクラス名を単純にルックCアップする場合、非仮想クラスAと仮想クラスの両方Aのクラス名が検出されるため、どのサブオブジェクトを初期化するかに関してあいまいなルックアップ結果が得られます。あなたのコードが不正な形式であることを意図している場合、標準を明確にする必要があると思います.


12.4/6コンストラクタについては、 のデストラクタについて次のように述べていることに注意してCください。

すべてのデストラクタは、修飾名で参照されているかのように呼び出されます。つまり、より派生したクラスで可能な仮想オーバーライド デストラクタは無視されます。

これは、次の 2 つの方法で解釈できます。

  • A::~A() の呼び出し
  • ::A::~A() の呼び出し

ここでは、標準があまり明確ではないように思えます。2 番目の方法では有効になります ( 3.4.3/6C++0x によって、両方のクラス名Aがグローバル スコープで検索されるため)、最初の方法では無効になります (どちらもA継承されたクラス名を見つけるため)。また、検索を開始するサブオブジェクトにも依存します (仮想基本クラスのサブオブジェクトを開始点として使用する必要があると思います)。このようになれば

virtual_base -> A::~A();

次に、仮想ベースのクラス名をパブリック名として直接見つけます。これは、派生クラスのスコープを調べて、アクセスできない名前を見つける必要がないためです。繰り返しますが、推論は似ています。検討:

struct A { };
struct B : A { };
struct C : B, A {
} c;

デストラクタが単純に を呼び出す場合、継承されたクラス名としてのthis->A::~A()ルックアップ結果があいまいになるため、この呼び出しは有効ではありAません (スコープから直接基底クラス オブジェクトの非静的メンバー関数を参照することはできません。CCを参照してください)。 10.1/3++03)。関連するクラス名を一意に識別する必要があり、 のようなクラスのサブオブジェクト参照で開始する必要がありa_subobject->::A::~A();ます。

于 2010-03-03T13:00:30.680 に答える
2

仮想基本クラスは常に、最も派生したクラス (ここでは C) から初期化されます。コンパイラーは、コンストラクターがアクセス可能であることを確認する必要があります (つまり、g++ 3.4 でエラーが発生します)。

class A { public: A(int) {} };
class B: private virtual A {public: B() : A(0) {} };
class C: public B {};

int main() {
    C c;
    return 0;
}

あなたの説明は何もないことを暗示していますが、ベースとして、 A がプライベートであるかどうかは問題ではありません(転覆するのは簡単です:) class C: public B, private virtual A

仮想基本クラスのコンストラクターが最も派生したクラスから呼び出される理由は、それらを基本クラスとして持つクラスの前に構築する必要があるためです。

編集: キリルは、私の読書と最近のコンパイラの動作とは異なる古いコアの問題について言及しました。いずれかの方法で標準的なリファレンスを取得しようとしますが、時間がかかる場合があります。

于 2010-03-03T12:52:54.007 に答える