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

时间:2017-12-05 14:33:56

标签: c++ constructor language-lawyer c++17 protected

我有这个测试用例:

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的一部分还是两个编译器中的错误?

2 个答案:

答案 0 :(得分:51)

自{+ C ++ 17以来aggregate的定义发生了变化。

在C ++ 17之前

  

没有基类

从C ++ 17开始

  

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

这意味着,对于BD,它们不是C ++ 17之前的聚合类型,而是B{}D{}value-initialization将被执行,然后将调用defaulted default constructor;这很好,因为基类的protected构造函数可以由派生类的构造函数调用。

从C ++ 17开始,BD成为聚合类型(因为它们只有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),具体如下:通常的列表初始化规则(使用默认构造函数执行非类类型和非聚合类的值初始化,以及聚合的聚合初始化)。如果引用类型的成员是其余成员之一,则该程序格式不正确。

这意味着将直接对基类子对象进行值初始化,绕过BD的构造函数;但是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中,CD现在是聚合,即使它们有基类。

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

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

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

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

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

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

= default工作的原因是因为它不是用户提供的构造函数。有关更多信息,请访问this question