GCCの仮想継承動作に関する奇妙なデフォルトの空のコンストラクター

Dec 14 2020

コード内のBaseクラスへの仮想継承を持つDerivedクラスの次の状況があります。

class Base {
    int x;
public:
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

リンク: https://godbolt.org/z/bn1EY6

GCC(トランク)は次のエラーを出します:error: use of deleted function 'Derived::Derived()'Clang(トランク)は問題なくコンパイルします。

コンストラクターをBaseクラスのDerived() {}代わりに変更するDerived() = defaultか、Baseクラスで空のコンストラクターを定義すると、GCCは機能します。

= defaultこの場合、GCCで関数を削除するのはなぜですか?

回答

2 eerorika Dec 14 2020 at 18:29

標準によると(最新のドラフト):

[class.default.ctor]

クラスXのデフォルトのデフォルトコンストラクタは、次の場合に削除済みとして定義されます。

  • Xは... [[適用されない]]の和集合です。
  • Xは、... [[適用されません]]を持つバリアントメンバーMを持つ非ユニオンクラスです
  • デフォルトのメンバー初期化子([class.mem])がない非静的データメンバーは参照型であり、[[適用されません]]
  • const修飾型の非バリアント非静的データメンバー... [[適用されません]]
  • Xは和集合であり、... [[適用されません]]
  • Xは非ユニオンクラスであり、匿名のユニオンメンバーのすべてのメンバー... [[適用されません]]
  • [ベースが潜在的に構築されたサブオブジェクトである場合に適用]中括弧または等しい初期化子を持つ非静的データメンバーを除き、潜在的に構築されたサブオブジェクトはクラスタイプM(またはその配列)を持ち、Mにはデフォルトコンストラクターがありませんまたは、Mの対応するコンストラクターを見つけるために適用されるオーバーロード解決([over.match])により、あいまいさが生じるか、デフォルトのデフォルトコンストラクターから削除またはアクセスできない関数が生成されます。
  • 構築される可能性のあるサブオブジェクトには、デフォルトのデフォルトコンストラクタから削除またはアクセスできないデストラクタを持つタイプがあります。[[適用されません]]

削除されるデフォルトのデフォルトコンストラクターに適用される可能性のあるルールは1つだけであり、ベースが潜在的に構築されたサブオブジェクトであるかどうかによって異なります。

[特殊]

クラス、その非静的データメンバー、その非仮想直接基本クラス、およびクラスが抽象でない場合([class.abstract])の場合、その仮想基本クラスは、潜在的に構築されたサブオブジェクトと呼ばれます。

Derivedは抽象的であり(すべての純粋仮想関数を実装していないため)、Base仮想ベースであるため、ベースは潜在的に構築されたサブオブジェクトではないため、削除されるデフォルトのコンストラクターに適用される唯一のルールは適用されません。したがって、削除しないでください。コンパイラが間違っています。


簡単な回避策(すでに述べたものをDerived::Derieved()除く)は、宣言しないことです。その場合、正しく暗黙的に生成されているようです。


noexceptを追加すると、エラー内部コンパイラエラーが発生します

これもコンパイラのバグです。

1 AdrianMole Dec 14 2020 at 18:02

この場合、=デフォルトでGCCの関数が削除されるのはなぜですか?

これがGCCのバグであるかどうか(MSVCは同様に動作しますが、clang-clはコードをそのまま受け入れます)は、C ++標準でさらに研究されている人々の問題です。しかし、コンパイラが取っているように見える= defaultことを意味するDerivedコンストラクタが依存する(またはあるに相当)のためのデフォルトコンストラクタBase-間違いなくされますが、別の(デフォルト以外の)コンストラクタを定義しているとして、削除を。

ただし、独自のデフォルトコンストラクターを明示的に追加しDerived() {}その暗黙の依存関係を削除します。

これは、Baseクラスのデフォルトコンストラクターを指定(つまり、削除解除)することで(GCCおよびMSVCで)確認されます。

class Base {
    int x;
public:
    Base() : x{0} {}  // Adding this removes the error!
//  Base() = default; // Also works
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

編集:これも関連している可能性があり、重複している可能性もあります:仮想継承でデフォルトコンストラクタが呼び出されるのはなぜですか?