我有这个测试用例:
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
(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.
这种行为改变是C ++ 17的一部分还是两个编译器中的错误?
答案 0 :(得分:51)
自{+ C ++ 17以来aggregate的定义发生了变化。
没有基类
没有
virtual, private, or protected (since C++17)
基类
这意味着,对于B
和D
,它们不是C ++ 17之前的聚合类型,而是B{}
和D{}
,value-initialization将被执行,然后将调用defaulted default constructor;这很好,因为基类的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)
,具体如下:通常的列表初始化规则(使用默认构造函数执行非类类型和非聚合类的值初始化,以及聚合的聚合初始化)。如果引用类型的成员是其余成员之一,则该程序格式不正确。
这意味着将直接对基类子对象进行值初始化,绕过B
和D
的构造函数;但是A
的默认构造函数是protected
,然后代码失败。 (请注意,A
不是聚合类型,因为它具有用户提供的构造函数。)
顺便说一句:C
(使用用户提供的构造函数)不是C ++ 17之前和之后的聚合类型,所以两种情况都可以。
答案 1 :(得分: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};
}
请注意,我们不是继承构造函数。在C ++ 17中,C
和D
现在是聚合,即使它们有基类。
使用{}
,聚合初始化开始,并且不发送任何参数将被解释为从外部调用父级的默认构造函数。
例如,可以通过将类D
更改为此来禁用聚合初始化:
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
这是因为当具有不同访问说明符的成员时,聚合初始化不适用。
= default
工作的原因是因为它不是用户提供的构造函数。有关更多信息,请访问this question。