Clang 8.0.0+および-std=c++17
でコンパイルされた次のコードでは、B{}
を使用して派生クラスインスタンスを作成すると、エラーerror: temporary of type 'A' has protected destructor
が発生します。テンポラリの型がA
である(したがって、パブリックデストラクタが必要な)場合、このメッセージにB
が表示されるのはなぜですか?
class A {
protected:
A() = default;
~A() = default;
};
class B : public A {
// can also omit these 3 lines with the same result
public:
B() = default;
~B() = default;
};
void foo(const B&) {}
int main() {
// error: temporary of type 'A' has protected destructor
foo(B{});
// ^
return 0;
}
これは、C++ 20以前の 集約初期化 の微妙な問題です。
C++ 20より前は、B
(およびA
)は 集合型 :
(強調鉱山)
ユーザー提供、継承、または明示的なコンストラクターはありません(明示的にデフォルトまたは削除されたコンストラクターは許可されています)(C++ 17以降)(C++ 20まで) )
その後
初期化句の数がメンバーの数より少ない場合
and bases (since C++17)
または初期化リストが完全に空の場合、残りのメンバーand bases (since C++17)
は空のリストによって初期化by their default member initializers, if provided in the class definition, and otherwise (since C++14)
されます。通常のリスト初期化規則(デフォルトのコンストラクターを使用して非クラス型および非集約クラスの値の初期化を実行し、集約の集約初期化を実行する)に従います。
したがって、_B{}
_は、集約初期化を介して一時オブジェクトを構築します。これにより、基本サブオブジェクトが空のリストで直接初期化されます。つまり、集約初期化を実行して、A
基本サブオブジェクトを構築します。 B
のコンストラクタはバイパスされることに注意してください。問題は、このような状況では、protected
記述子を呼び出して、タイプA
の直接作成された基本サブオブジェクトを破棄できないことです。 (protected
の集約初期化によってもバイパスされるため、A
コンストラクターについて不満はありません。)
これをfoo(B());
に変更して、集計の初期化を回避できます。 B()
は value-initialization を実行し、一時オブジェクトはB
のコンストラクターによって初期化されます。
ところで、C++ 20以降のコードは正常に動作します。
ユーザー宣言または継承コンストラクターなし(C++ 20以降)
B
(およびA
)は、集約型ではなくなりました。 _B{}
_は リストの初期化 を実行し、一時オブジェクトはB
のコンストラクターによって初期化されます。効果はB()
と同じです。