web-dev-qa-db-ja.com

「タイプ 'A'の一時はデストラクタを保護しています」が、そのタイプはBです。

Clang 8.0.0+および-std=c++17でコンパイルされた次のコードでは、B{}を使用して派生クラスインスタンスを作成すると、エラーerror: temporary of type 'A' has protected destructorが発生します。テンポラリの型がAである(したがって、パブリックデストラクタが必要な)場合、このメッセージにBが表示されるのはなぜですか?

https://godbolt.org/z/uOzwYa

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;
}
10
jtbandes

これは、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()と同じです。

11
songyuanyao