[英]copy-list-initialization of non-copyable types
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
)。
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 = {...};
作为复制列表初始化 。 你的推理出错的地方在于你从未想过复制列表初始化实际上做了什么。 没有复制; 这就是所谓的 。
副本列表初始化 列表是初始化的一个子集。 因此,它遵循8.5.4,p3规定的所有规则。 我不打算在这里引用它们,因为它们有几页长。 我将简单解释这些规则如何适用于complex g = {1, 2};
, 为了:
complex
不是聚合,因此该规则不计算在内。 complex
不是initializer_list
,所以此规则不计算在内。 因此,不会创建和复制/移动临时。
复制列表初始化和直接列表初始化之间的唯一区别在13.3.1.7,p1中说明:
[...]在复制列表初始化中,如果选择了显式构造函数,则初始化是错误的。
这是complex g{1, 2}
和complex g = {1, 2}
complex g{1, 2}
之间的唯一区别 。 它们都是list-initialization
示例,除了使用显式构造函数外,它们以统一的方式工作。
构造函数来自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中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.