我真的需要为const对象实现用户提供的构造函数吗?

时间:2014-02-20 06:44:44

标签: c++ c++11 constructor const language-lawyer

我有代码:

class A {
  public:
    A() = default;

  private:
    int i = 1;
};

int main() {
  const A a;
  return 0;
}

它在g ++上编译得很好(参见ideone),但在clang ++上失败并出错:

  

const类型'const A'的对象的默认初始化需要用户提供的默认构造函数

我在LLVM bug-tracker上报告了此问题并将其视为无效。

我认为试图说服铿锵的开发者绝对毫无意义。另一方面,我没有看到这种限制的原因。


有人可以建议,如果C ++ 11 Standard以某种方式暗示这段代码无效吗?或者我应该向g ++报告错误?或许在语言规则方面有足够的自由来以多种方式处理这些代码?

4 个答案:

答案 0 :(得分:23)

N3797§8.5/ 7说:

  

如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型。

没有进一步的例子或解释。我同意这看起来很奇怪。此外,在C ++ 11中更新规则比在C ++ 03中更严格,当类类型需要用户声明的构造函数时。 (您的构造函数是用户声明的。)

解决方法是使用{}请求值初始化,或使用Dietmar的聪明的类外inline定义。

如果你在没有初始化程序的情况下添加另一个成员,GCC确实提供了一个诊断(非常好的,指的是更新的C ++ 11要求)。

  private:
    int i = 1;
    int j;

unmem.cpp:11:11: error: uninitialized const ‘a’ [-fpermissive]
   const A a;
           ^
unmem.cpp:1:7: note: ‘const class A’ has no user-provided default constructor
 class A {
       ^
unmem.cpp:3:5: note: constructor is not user-provided because it is explicitly defaulted in the class body
     A() = default;
     ^
unmem.cpp:7:9: note: and the implicitly-defined constructor does not initialize ‘int A::j’
     int j;

GCC source引用DR 253为什么必须初始化空对象或完全初始化的const对象?这是标准中的一个开放问题,最后一次更新于八月2011年(后C ++ 11),附注:

  

如果隐式默认构造函数初始化所有子对象,则不需要初始化程序。

因此,虽然Clang符合C ++ 11(并且将遵循C ++ 14的原则),但GCC正在实施标准化委员会的最新思想。

提起GCC bug。我预测你需要-pedantic才能在错误修复的时候得到诊断。

答案 1 :(得分:22)

请注意,您可以轻松地将您的类转换为具有用户定义的默认构造函数的类:

class A {
  public:
    A();

  private:
    int i = 1;
};

inline A::A() = default;

根据8.4.2 [dcl.fct.def.default]第4段:

  

...如果特殊成员函数是用户声明的而非显式的,则由用户提供   在第一次声明中违约或删除。 ...

这隐含地指出,在第一个声明中未明确默认的函数不是用户提供的。结合8.5 [dcl.init]第6段

  

...如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型。

很明显,无法使用默认构造函数默认在其第一个声明中初始化const对象。但是,如果不是上面代码中的第一个声明,则可以使用默认定义。

答案 2 :(得分:4)

编辑:以下内容基于过时的信息。我刚刚通过了N3797,这就是我发现的:

  

§8.5/ 7 [dcl.init]
  如果程序要求默认初始化   对于const限定类型T的对象,T应该是一个类类型   a 用户提供的默认构造函数。

请注意以下链接中的标准引用用户声明的


以下程序用g ++编译但不用clang ++:

struct A {};

void f()
{
  A const a;
}

它可能与此bug report有关,在那里它是“固定的”。除非它们已初始化,否则g ++一旦包含数据成员就无法编译它。请注意,int member = 1将不再使A成为POD。相比之下,clang ++拒绝所有排列(空类和数据成员初始化或未初始化。)对于以下段落对标准的含义的解释:

  

§8.5/ 9 [dcl.init]说:

     

如果没有为对象指定初始化程序,并且该对象是   (可能是cv限定的)非POD类类型(或其数组),.   对象应默认初始化;如果对象是   const-qualified类型,底层类类型应具有   用户声明的默认构造函数。否则,如果没有初始化器   为对象指定的对象及其子对象(如果有)具有   不确定的初始值;如果对象或其任何子对象   该程序是const-qualified类型,程序结构不合理。

Why does C++ require a user-provided default constructor to default-construct a const object?。据推测,该程序格式不正确if the object is of const-qualified POD type, and there is no initializer specified (because POD are not default initialized).请注意g ++如何表现如下:

struct A {int a;};
struct B {int a = 1;};
int main() 
{
    A a;
    B b;
    const A c; // A is POD, error
    const B d; // B is not POD, contains data member initializer, no error
}

答案 3 :(得分:2)

自C ++ 17起,此代码正确,this question中的类似代码也是如此:

struct MyClass1 { int i{}; };
struct MyClass2 { const MyClass1 m; };
MyClass2 a;

clang 8.0.0即使使用-std=c++17也拒绝后者的代码,这意味着clang 8.0.0有一个错误。

在C ++ 17中,以下新文本被添加为[dcl.init] / 7(根据P0490R0响应DR 253):

  

如果T的默认初始化会调用用户提供的T的构造函数(不是继承的),则类类型T const-default-constructible 来自基类)或

     
      
  • M的每个直接非变量非静态数据成员T都有一个默认的成员初始化程序,或者如果M是类类型X(或其数组) ),X是const-default-constructible的,
  •   
  • 如果T是具有至少一个非静态数据成员的联合,则恰好一个变体成员具有默认的成员初始值设定项,
  •   
  • 如果T不是联合,则对于每个至少具有一个非静态数据成员的匿名联合成员,恰好一个非静态数据成员具有默认成员初始化程序,并且
  •   
  • 每个T可能构造的基类都是const-default-constructible的。
  •   
     

如果程序要求对const限定类型T的对象进行默认初始化,则T应该是const-default可构造的类类型或其数组。


在C ++ 17之前,没有这样的文字;定义为const的对象必须具有初始化程序或用户提供的构造函数。因此,在C ++ 17之前,clang是正确的,并且g ++被错误地接受了未经诊断的代码。