不可复制类型的复制列表初始化

copy-list-initialization of non-copyable types

本文关键字:列表 初始化 复制 可复制 类型      更新时间:2023-10-16

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-列表初始化是从一个有支撑的init列表。这样的初始化器被称为初始化器列表,并且列表中以逗号分隔的初始值设定项子句被称为初始值设定项列表的元素。初始值设定项列表可能为空。列表初始化可以发生在直接初始化或复制初始化中语境;中的列表初始化直接初始化上下文称为直接列表初始化调用复制初始化上下文中的列表初始化复制列表初始化。

原子学的问题

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

修复

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

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开始,第16页:

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

  • 如果初始值设定项是一个(非括号)支撑的初始列表,则对象或引用被列表初始化(8.5.4)

这意味着复制初始化规则不会将应用于支持的初始化列表。它们使用一套单独的规则,如8.5.4所述。

您引用了8.5.4,其中将T x = {...};定义为复制列表初始化。您的推理出错的地方是,您从未查找复制列表初始化实际执行的操作。没有复制;这就是所称的

复制列表初始化列表初始化

  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的示例,除了使用显式构造函数外,它们以统一的方式工作。

-T的构造函数是而不是显式的,复制列表初始化与复制初始化不同。两者都会导致"考虑构造函数",但副本初始化总是"考虑"副本的缺点;struc­tor,而列表初始化则考虑填充了列表元素的构造函数(加上一些细节)。也就是说:

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中规定了所有必要的内容。