copy-list-initial of non-copyable types

时间:2012-12-03 23:49:21

标签: c++ c++11

12.6.1 - 显式初始化

struct complex {
  complex();
  complex(double);
  complex(double,double);
};

complex sqrt(complex,complex);

complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g

8.5初始化程序

  

14 - 以

形式出现的初始化      

T x = a;

     

以及在参数传递,函数返回,抛出异常   (15.1),处理异常(15.3)和聚合成员   初始化(8.5.1)称为复制初始化。 [注意:   复制初始化可以调用移动(12.8)。 - 结束说明]

     

15 - 表格中出现的初始化

     

T x(a);

     

T x{a};

     

以及新表达式(5.3.4),static_cast表达式   (5.2.9),功能符号类型转换(5.2.3),基数和   成员初始值设定项(12.6.2)称为直接初始化

8.5.4列表初始化[dcl.init.list]

  

1 - 列表初始化是从中初始化对象或引用   一个braced-init-list。这样的初始化程序称为初始化程序列表,   和列表中逗号分隔的初始化子句称为   初始化列表的元素。初始化列表可以为空。   列表初始化可以在直接初始化或复制初始化中进行   上下文;列表初始化在   直接初始化上下文称为直接列表初始化和   调用复制初始化上下文中的列表初始化   复制列表初始化。

原子的问题

29.6.5对原子类型的操作要求[atomics.types.operations.req]

  

#define ATOMIC_VAR_INIT(value)见下文

     

宏扩展为适合常量的标记序列   初始化一个静态存储持续时间的原子变量   与值初始化兼容的类型。 [注意:这个   操作可能需要初始化锁。 - 结束说明]并发访问   对于正在初始化的变量,即使是通过原子操作,   构成数据竞赛。 [例如:

     

atomic<int> v = ATOMIC_VAR_INIT(5);

根据前面的部分,似乎不应该在没有复制构造函数的情况下进行赋值初始化,即使它根据§12.8.31和§12.8.32被省略,但是原子被定义为:

29.5原子类型[atomics.types.generic]

atomic() noexcept = default;
constexpr atomic(T) noexcept;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
T operator=(T) volatile noexcept;
T operator=(T) noexcept;

没有复制构造函数!

ATOMIC_VAR_INIT经常扩展为大括号初始化的大括号表达式,但是atomic<int> v = {5}仍然是一个赋值初始化,并且意味着在直接构造临时构造后复制构造。

我查看了“常量初始化”部分,看看是否存在允许没有副本的漏洞(因为“宏扩展到适合于静态存储持续时间的原子变量的常量初始化的令牌序列”与值初始化兼容的类型“)但我已经放弃了。

相关讨论:

http://thread.gmane.org/gmane.comp.lib.qt.devel/8298

http://llvm.org/bugs/show_bug.cgi?id=14486

修改

在建立演绎过程时引用相关标准部分的答案将是理想的。

结论

因此,在Nicol Bolas的好回答之后,有趣的结论是complex g = { 1, 2 }是一个副本(它是复制初始化上下文),它不复制(复制列表初始化解析像直接列表 - 初始化)标准表明存在复制操作(12.6.1:...and copy/move it into g)。

FIX

拉取请求:https://github.com/cplusplus/draft/pull/37

2 个答案:

答案 0 :(得分:10)

complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g

这是不真实的。而且我不是说复制/移动将被省略;我的意思是没有复制或移动。

您引用了8.5 p14,它将T x = a;定义为复制初始化。这是真的。但它继续定义初始化实际上是如何工作的:

从8.5,p16:

  

初始化器的语义如下。目标类型是要初始化的对象或引用的类型,源类型是初始化表达式的类型。如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型。

     
      
  • 如果初始化程序是(非括号的)braced-init-list,则对象或引用是列表初始化的(8.5.4)。
  •   

这就意味着复制初始化规则不适用于 braced-init-list 。他们使用一套单独的规则,如8.5.4所述。

你引用了8.5.4,它将T x = {...};定义为 copy-list-initialization 。您的推理出错的地方在于您从未查找 copy-list-initialization 实际 的内容。没有复制;这就是所谓的

copy-list-initialization list-initialization 的子集。因此,它遵循8.5.4,p3规定的所有规则。我不打算在这里引用它们,因为它们有几页长。我只是按顺序解释规则如何适用于complex g = {1, 2};

  1. 初始化列表包含元素,因此此规则不计算在内。
  2. complex不是聚合,因此此规则不计算在内。
  3. complex不是initializer_list的专精,因此此规则不计算在内。
  4. 根据13.3和13.3.1.7的规则,通过重载决议考虑适用的构造函数。这会找到需要两个双精度的构造函数。
  5. 因此,不会创建和复制/移动临时。

    copy-list-initialization direct-list-initialization 之间的唯一区别在13.3.1.7中说明,p1:

      

    [...]在copy-list-initialization中,如果选择了显式构造函数,则初始化是不正确的。

    complex g{1, 2}complex g = {1, 2}之间的唯一差异。它们都是list-initialization的示例,除了使用显式构造函数外,它们以统一的方式工作。

答案 1 :(得分:8)

构造函数 - 来自T 显式,并且copy-list-initialization与copy-initialization不同。两者都会导致“构造函数被考虑”,但复制初始化总是“考虑”复制构造函数,而列表初始化则考虑填充了列表元素的构造函数(加上一些细节)。即:

struct Foo
{
    Foo(int) {}
    Foo(Foo const &) = delete;
};

int main()
{
    Foo f = { 1 };  // Fine
}

(如果构造函数为explicit,则会失败。此外,Foo x = 1;当然会因删除的复制构造函数而失败。)

也许是一个更具启发性的用例:

Foo make() { return { 2 }; }

void take(Foo const &);
take(make());

为此所需的一切都在8.5.4 / 3和13.3.1.7/1中。