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

标签 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 - The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [Note: Copy-initialization may invoke a move (12.8). — end note ]

15 - The initialization that occurs in the forms

T x(a);

T x{a};

as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.

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

1 - List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the list are called the elements of the initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.

原子的问题

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

#define ATOMIC_VAR_INIT(value) see below

The macro expands to a token sequence suitable for constant initialization of an atomic variable of static storage duration of a type that is initialization-compatible with value. [Note: This operation may need to initialize locks. — end note ] Concurrent access to the variable being initialized, even via an atomic operation, constitutes a data race. [ Example:

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}仍然是赋值初始化,意味着在直接构建临时对象之后进行复制构建。

我查看了“常量初始化”部分,看看是否存在允许在没有拷贝的情况下进行此操作的漏洞(因为“宏扩展为适合常量初始化原子变量的 token 序列,该原子变量的静态存储持续时间为与值初始化兼容的类型”),但我已经放弃了。

相关讨论:

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; 定义为 copy-initialization。这是真实的。但它接着定义了初始化的实际工作方式:

从 8.5 开始,第 16 页:

The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).

那里的权利意味着复制初始化规则不适用于braced-init-list。他们使用一组单独的规则,如 8.5.4 中所述。

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

copy-list-initializationlist-initialization 的一个子集。因此,它遵循 8.5.4,p3 规定的所有规则。我不打算在这里引用它们,因为它们有好几页那么长。我将按顺序简单解释规则如何应用于 complex g = {1, 2};:

  1. 初始化列表有元素,所以这条规则不算数。
  2. complex 不是聚合,因此这条规则不算在内。
  3. complex 不是 initializer_list 的特化,因此这条规则不算在内。
  4. 根据 13.3 和 13.3.1.7 的规则,通过重载决策考虑适用的构造函数。这会找到采用两个 double 的构造函数。

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

copy-list-initializationdirect-list-initialization 之间的唯一区别在 13.3.1.7, p1:

[...] In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

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

关于c++ - 不可复制类型的复制列表初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13693871/

相关文章:

c++ - 将 vector 传递给某些需要某种大小的数组引用的遗留 API 的任何方法

c++ - 当可以使用RVO时,为什么要按shared_ptr而不是按值返回?

c++ - 如何避免使用已知函数参数的 lambda 函数?

c++ - 内部模板类继承

c++ - 虚拟功能实现

c++ - 为什么我的直播摄像头在使用 OpenCV+Qt 时严重滞后

c++ - vs 2008 部署项目不工作

c++ - 初始化器列表和运算符的 RHS

c++ - 什么时候私有(private)构造函数不是私有(private)构造函数?

C++ gsoap mime/dime 用于 Windows 中的二进制文件