删除复制构造函数时,为什么默认初始化失败?

时间:2014-04-25 22:20:04

标签: c++ c++11

有人可以向我解释为什么以下不编译?我不确定编译器为什么认为我正在调用复制构造函数。

struct test {
  const int index;
 private:
  test(const test&) = delete; // comment out this line and voila.
};

int main(int argc, char** argv) {
  test arg{1};
  return arg.index;
}

GCC因此消息失败(可在http://www.compileonline.com/compile_cpp11_online.php中重现)

main.cpp: In function ‘int main(int, char**)’:
main.cpp:8:13: error: no matching function for call to ‘test::test(<brace-enclosed initializer list>)’
   test arg{1};
             ^
main.cpp:8:13: note: candidate is:
main.cpp:4:3: note: test::test(const test&) <deleted>
   test(const test&) = delete;
   ^
main.cpp:4:3: note:   no known conversion for argument 1 from ‘int’ to ‘const test&’

3 个答案:

答案 0 :(得分:6)

这里似乎有两个问题。标题询问 default-initialization ,您的代码使用 list-initialization


您依赖于列表初始化,而不是默认初始化,特别是您正在尝试进行聚合初始化。其规则见8.5.4p3:

  

类型T的对象或引用的列表初始化定义如下:

     
      
  • 如果T是聚合,则执行聚合初始化(8.5.1)。
  •   
     

...

     
      
  • 否则,如果T是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数。如果转换任何参数需要缩小转换(见下文),则程序格式不正确。
  •   

和8.5.1:

  

聚合是一个数组或类(第9条),没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),没有虚拟功能(10.3)。

     

当初始化列表初始化聚合时,如8.5.4中所述,初始化列表的元素被视为聚合成员的初始化者,增加下标或成员顺序。

这正是你想要的,但拥有一个用户提供的构造函数&#34;禁用它。这与&#34;用户声明的构造函数&#34;不同,但是一些编译器编写者可能会混淆这两者(参见@dyp's answer)。

据我所知,没有办法为不是聚合的类型显式启用聚合初始化。但是你可以解决这个问题。使您的类型成为聚合,并以另一种方式禁用复制构造:

struct test
{
  const int index;
  struct nocopy { nocopy() = default; nocopy(const nocopy&) = delete; } copy_disabled;
};

这是有效的,因为12.8p11说:

  

隐式声明的复制/移动构造函数是其类的内联公共成员。如果X具有以下内容,则将类X的默认复制/移动构造函数定义为已删除(8.4.3):

     

...

     
      
  • 类型M(或其数组)的非静态数据成员,由于重载决策(13.3)无法复制/移动,因为应用于M的相应构造函数,导致模糊或从默认构造函数中删除或无法访问的函数,
  •   

但请注意,您不能继承boost::noncopyable,因为聚合不能包含基类。


处理默认初始化的情况稍微容易一些。回想一下编译器声明的默认构造函数的条件:

  • 没有用户声明的构造函数。

由于您已经声明了构造函数(并将其定义为已删除),因此您还可以删除默认的默认构造函数。

此规则来自标准中的12.1p4:

  

X的默认构造函数是类X的构造函数,可以在没有参数的情况下调用它。 如果类X没有用户声明的构造函数,则没有参数的构造函数被隐式声明为默认(8.4)。隐式声明的默认构造函数是其类的inline public成员。如果...... {/ p>,则将类X的默认默认构造函数定义为已删除

由于默认初始化依赖于编译器声明的默认构造函数,因此当且仅当没有用户声明的构造函数时,它才有效。

您可以通过将默认构造函数显式声明为默认值来解决此问题,如下所示:

struct test
{
  const int index;
  test(void) = default; // <-- ADD THIS
private:
  test(const test&) = delete;
};

但是这在你的情况下不起作用,默认的默认构造函数被删除,因为规则继续

  

如果......

,则将类X的默认默认构造函数定义为已删除      
      
  • const的任何非变体非静态数据成员 - 没有 brace-or-equal-initializer 的限定类型(或其数组)没有用户提供的默认值构造,
  •   

答案 1 :(得分:3)

这是一个gcc错误,请参阅Bug 52707 - C++11 Deleted special member function prevent type being an aggregate 。 gcc错误地认为该类不再是聚合,如果它具有已删除的特殊成员函数(如删除的复制构造函数)。

这似乎已经在g ++ 4.9.0中修复了

答案 2 :(得分:1)

不是。该错误表明:

no matching function for call to ‘test::test(<brace-enclosed initializer list>)

这是你想要做的。

因为G ++很好,它列出了可能的候选者,其中包括:

test::test(const test&) <deleted>

然后它会注意到,这不仅会被删除,test arg{1};将无效,因为您无法将1转换为test个对象。