10

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};

以及在 new 表达式 (5.3.4)、static_cast 表达式 (5.2.9)、函数符号类型转换 (5.2.3) 以及基类和成员初始化器 (12.6.2)中称为直接初始化

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

1 - 列表初始化是从一个花括号初始化列表初始化一个对象或引用。这样的初始化器称为初始化器列表,列表中以逗号分隔的初始化器子句称为初始化器列表的元素。初始化列表可能为空。列表初始化可以发生在直接初始化或复制初始化上下文中;直接初始化上下文中的列表 初始化称为直接列表初始化,而复制初始化上下文中的列表初始化称为复制列表初始化。

原子问题

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

#define ATOMIC_VAR_INIT(value)见下文

该宏扩展为一个令牌序列,适用于对与值初始化兼容的类型的静态存储持续时间的原子变量的常量初始化。[注意:此操作可能需要初始化锁。— end note ] 对正在初始化的变量的并发访问,即使是通过原子操作,也构成了数据竞争。[ 例子:

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)。

使固定

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

4

2 回答 2

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

这是不真实的。我并不是说复制/移动会被忽略;我的意思是不会有复制或移动。

您引用了 8.5 p14,它定义T x = a;copy-initialization。这是真的。但它接着定义了初始化的实际工作方式:

从 8.5,第 16 页开始:

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

  • 如果初始值设定项是(无括号的)大括号初始化列表,则对象或引用是列表初始化的(8.5.4)。

那里的权利意味着复制初始化规则不适用于花括号初始化列表。它们使用一组单独的规则,如 8.5.4 中所述。

您引用了 8.5.4,它定义T x = {...};copy-list-initialization。您的推理出错的地方是您从未查看过copy-list-initialization 的实际作用。没有复制;这就是它的名字

复制列表初始化是列表初始化的一个子。因此,它遵循 8.5.4, p3 规定的所有规则。我不打算在这里引用它们,因为它们有几页长。我将按complex g = {1, 2};顺序简单地解释规则如何适用于 :

  1. 初始化器列表有元素,所以这条规则不算数。
  2. complex不是聚合,所以这条规则不算数。
  3. complex不是 的特化initializer_list,所以这条规则不算。
  4. 根据 13.3 和 13.3.1.7 的规则,通过重载决议考虑适用的构造函数。这会找到需要两个双精度的构造函数。

因此,不会创建和复制/移入临时文件。

复制列表初始化直接列表初始化之间的唯一区别在 13.3.1.7, p1 中说明:

[...] 在复制列表初始化中,如果选择了显式构造函数,则初始化格式错误。

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

于 2012-12-04T00:52:50.287 回答
8

constructor-from-T不是式的,并且复制列表初始化与复制初始化不同。两者都导致“考虑构造函数”,但复制初始化总是“考虑”复制构造函数,而列表初始化考虑填充列表元素的构造函数(加上一些细节)。以机智:

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 中。

于 2012-12-03T23:58:27.597 回答