web-dev-qa-db-ja.com

C ++ 17で保護されたコンストラクターのルールを変更しましたか?

私はこのテストケースを持っています:

struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };

int main(){
    (void)B{};
    (void)C{};
    (void)D{};
}

Gccとclangの両方が、C++ 11およびC++ 14モードでコンパイルします。どちらもC++ 17モードで失敗します。

$ clang++ -std=c++17 main.cpp 
main.cpp:7:10: error: base class 'A' has protected default constructor
        (void)B{};
                ^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
                     ^
main.cpp:9:10: error: base class 'A' has protected default constructor
        (void)D{};
                ^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
                     ^
2 errors generated.

$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix

(master Branch 2017-12-05からコンパイルされたclang。)

$ g++ -std=c++17 main.cpp 
main.cpp: In function 'int main()':
main.cpp:7:10: error: 'A::A()' is protected within this context
  (void)B{};
          ^
main.cpp:1:22: note: declared protected here
 struct A{ protected: A(){} };
                      ^
main.cpp:9:10: error: 'A::A()' is protected within this context
  (void)D{};
          ^
main.cpp:1:22: note: declared protected here
 struct A{ protected: A(){} };
                      ^

$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

この動作の変更はC++ 17の一部ですか、それとも両方のコンパイラのバグですか?

60
Benjamin Buch

aggregate の定義はC++ 17以降に変更されました。

C++ 17より前

基本クラスなし

C++ 17以降

virtual, private, or protected (since C++17)基本クラスなし

つまり、BおよびDの場合、C++ 17より前では集約型ではなく、_B{}_および_D{}_の場合、 value-初期化 が実行され、次に デフォルトのデフォルトコンストラクター が呼び出されます。基本クラスのprotectedコンストラクターは、派生クラスのコンストラクターによって呼び出すことができるため、これは問題ありません。

C++ 17以降、BおよびDは集約型になります(これらはpublicベースクラスのみを持つため、クラスDの場合、明示的にC++ 11)以降、デフォルトのデフォルトコンストラクターが集約タイプに許可され、その後_B{}_および_D{}_に対して aggregate-initialization が実行されます。

direct public base, (since C++17)配列要素、または非静的クラスメンバは、クラス定義内の配列添え字/出現順に、初期化子リストの対応する句からコピー初期化されます。

初期化節の数がメンバーの数and bases (since C++17)より小さい場合、または初期化リストが完全に空の場合、残りのメンバーand bases (since C++17)は空のリストによってby their default initializers, if provided in the class definition, and otherwise (since C++14)が初期化されます。通常のリスト初期化ルール(デフォルトのコンストラクターを使用した非クラス型および非集約クラスの値初期化、および集約の集約初期化を実行します)に従います。参照型のメンバーがこれらの残りのメンバーの1つである場合、プログラムは不正な形式です。

つまり、基本クラスのサブオブジェクトは直接値で初期化され、BDのコンストラクターはバイパスされます。ただし、Aのデフォルトコンストラクターはprotectedであるため、コードは失敗します。 (Aはユーザー指定のコンストラクターを持っているため、集約型ではないことに注意してください。)

BTW:C(ユーザー提供のコンストラクターを使用)はC++ 17の前後では集約型ではないため、どちらの場合でも問題ありません。

51
songyuanyao

C++ 17では、集計に関するルールが変更されました。

たとえば、今すぐC++ 17でこれを行うことができます。

struct A { int a; };
struct B { B(int){} };

struct C : A {};
struct D : B {};

int main() {
    (void) C{2};
    (void) D{1};
}

コンストラクターを継承していないことに注意してください。 C++ 17では、CおよびDがベースクラスを持っている場合でも集約になりました。

{}を使用すると、集約の初期化が開始され、パラメーターの送信は、外部から親のデフォルトコンストラクターを呼び出すのと同じように解釈されます。

たとえば、クラスのDを次のように変更すると、集計の初期化を無効にできます。

struct B { protected: B(){} };

struct D : B {
    int b;
private:
    int c;
};

int main() {
    (void) D{}; // works!
}

これは、異なるアクセス指定子を持つメンバーがいる場合、集計の初期化は適用されないためです。

= defaultが機能する理由は、ユーザー提供のコンストラクタではないためです。詳細は この質問 で。

22