13

请考虑以下代码:

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(或更高版本)针对列表初始化指定了哪些规则。提前致谢。

4

1 回答 1

2

一遍又一遍地阅读标准,我认为这是一个错误。

标准是怎么说的?

8.5.1/2:当聚合被初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。每个成员都是从相应的初始化子句复制初始化的。

说明:

8.5/14 : (...) 称为复制初始化。[注意:复制初始化可能会调用移动(12.8)。——尾注]

但是我在 12.8 中没有发现任何证据表明在您的具体情况下需要采取行动。

8.5.4/3否则,如果 T 是类类型,则考虑构造函数。如果 T 有一个初始化列表构造函数,则参数列表由初始化列表作为单个参数组成;否则,参数列表由初始化列表的元素组成。枚举适用的构造函数,并通过重载决议 (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 成功的事实看起来非常像一个错误。

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

因此,我报告了这个错误

于 2015-01-31T15:27:46.720 回答