在C++ 17中更改了受保护构造函数的规则?

Ben*_*uch 60 c++ constructor protected language-lawyer 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{};
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

(clang于2017-12-05主编编译.)

$ 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.
Run Code Online (Sandbox Code Playgroud)

这种行为的改变是C++ 17的一部分还是两个编译器中的错误?

son*_*yao 51

自C++ 17以来,聚合的定义发生了变化.

在C++之前17

没有基类

自C++ 17

没有virtual, private, or protected (since C++17)基类

这意味着,对于BD,它们在C++ 17之前不是聚合类型,然后对于B{}D{},将执行值初始化,然后将调用默认的默认构造函数 ; 这很好,因为protected基类的构造函数可以由派生类的构造函数调用.

从C++ 17开始,BD成为聚合类型(因为它们只有public基类,并注意对于类D,从C++ 11开始,允许显式默认的默认构造函数为聚合类型),然后为B{}D{},聚合初始化将是执行,

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)由空列表,按照通常的列表初始化规则(其执行值初始化用于非类类型和具有默认构造函数的非聚合类,以及聚合的聚合初始化).如果引用类型的成员是其余成员之一,则该程序格式错误.

这意味着基类的子对象将值初始化直接的构造BD被旁路; 但是默认的构造函数Aprotected,那么代码就失败了.(注意,A它不是聚合类型,因为它具有用户提供的构造函数.)

BTW :( C使用用户提供的构造函数)不是C++ 17之前和之后的聚合类型,因此它适用于两种情况.

  • 这是一个破坏变化的人为例子 (6认同)

Gui*_*cot 22

在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};
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们不是继承构造函数.在C++ 17,CD现在聚集,即使它们具有基类.

使用{},聚合初始化开始,并且不发送任何参数将被解释为与从外部调用父级的默认构造函数相同.

例如,可以通过将类D更改为此来禁用聚合初始化:

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

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

int main() {
    (void) D{}; // works!
}
Run Code Online (Sandbox Code Playgroud)

这是因为当具有不同访问说明符的成员时,聚合初始化不适用.

= default作品的原因是因为它不是用户提供的构造函数.有关此问题的更多信息.