c++ - c++11列表初始化的不同行为

标签 c++ c++11 list-initialization

请考虑以下代码:

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&) = delete;
  A(A&&) = delete;
  A(const std::string &a) : s(a) {}
};

现在,我想使用列表初始化来初始化 A 的数组。 g++ (4.9.1) 可以成功构建以下代码:

int main() {
  A arr[2] = {{"a"}, {"b"}};
  return 0;
}

但是,下面的代码失败了:

class Aggr {
private:
  A arr[2];
public:
  Aggr() : arr{{"a"}, {"b"}} {}
};

错误信息是,

test.cc: In constructor ‘Aggr::Aggr()’:
test.cc:22:28: error: use of deleted function ‘A::A(A&&)’
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^          
test.cc:11:3: note: declared here
   A(A&&) = delete;
   ^

也就是说,列表初始化器尝试调用移动构造函数来初始化类内部的数组。然而,该代码是由 clang v3.5 成功构建的,没有任何警告。 因此,我想知道 C++11(或更高版本)针对列表初始化指定了哪些规则。提前致谢。

最佳答案

反复阅读标准,我认为这是一个错误。

标准是怎么说的?

8.5.1/2 : When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

说明:

8.5/14 : (...) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]

但我在 12.8 中没有发现任何证据表明在您的特定情况下需要移动。

8.5.4/3 Otherwise, if T is a class type, constructors are considered. If T has an initializer-list constructor, the argument list consists of the initializer list as a single argument; otherwise, the argument list consists of the elements of the initializer list. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3).

所以原则上你的代码应该可以工作!

这是一个错误吗?尝试实验方式

我注释掉了移动构造函数的删除,以便从隐式移动构造函数中受益。奇怪的是,然后我收到以下错误消息:

    Compilation error   time: 0 memory: 3232 signal:0

prog.cpp: In constructor 'Aggr::Aggr()':
prog.cpp:19:28: error: use of deleted function 'A::A(const A&)'
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^
prog.cpp:10:3: note: declared here
   A(const A&) = delete  

所以现在他提示缺少复制构造函数!

更奇怪的是,我提供了自己的移动构造函数而不是隐式构造函数:在这里它成功编译了代码!

最后我提供了一个拷贝和一个移动,并添加了一些跟踪:

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&)  { std::cout<<"copy\n";} //= delete;
  A(A&&) { std::cout<<"move\n";} //= delete;
  A(const std::string &a) : s(a) {  std::cout<<"string ctor\n";}
};

当我创建一个 Aggr 对象时,它只显示:

string ctor 
string ctor

显示数组成员是使用复制省略从字符串构造函数初始化的,正如我们所期望的那样。

所有这些测试都是在带有 C++14 选项的 ideone 上使用 gcc-9.4.2 执行的。

结论

相同的代码使用隐式移动 ctor 编译失败而使用用户定义的移动 ctor 编译成功的事实看起来非常严重,就像一个错误。

移动构造函数在可用时不使用这一事实强化了这种印象。

因此,我报告了 this bug .

关于c++ - c++11列表初始化的不同行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28184984/

相关文章:

c++ - 动态控制c++数据结构中的成员数量

c++ - 当构造函数具有相同的参数类型(文件路径)时,如何从数组创建(初始化)std::tuple

c++ - 用于继承的模板便捷构造函数

c++ - 如何删除元组初始化 vector 中的样板?

c++ - 模板打印函数 C++

c++ - 使用尾递归实现 Tak 函数

C++ 将 0.00 转换为 00.00

c++ - Cygwin 不编译 stod

c++ - Visual C++ 初始化与 gcc 和 clang 不一致

c++ - 这种类似列表的初始化是如何工作的?