C++17 中不可复制变量的成员初始化

Member initialization for non-copyable variable in C++17

在对不可复制变量(如std::atomic<int>)进行成员初始化时,根据答案here,需要使用direct-initialization而不是copy-initialization。但是,当我在 g++ 7.4.0 中打开 -std=c++17 时,似乎后者也能正常工作。

#include <atomic>

class A {
    std::atomic<int> a = 0;     // copy-initialization
    std::atomic<int> b{0};      // direct-initialization
};
$ g++ -c atomic.cc -std=c++11    // or c++14
atomic.cc:4:26: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
     std::atomic<int> a = 0;     // copy-initialization

$ g++ -c atomic.cc -std=c++17
// no error

使用 g++ 6.5.0 编译时也失败,即使使用 -std=c++17。哪一个是正确的?

自 C++17 以来行为发生了变化,要求 编译器省略 std::atomic<int> a = 0; 中的 copy/move 构造,即 guaranteed copy elision

(强调我的)

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

详细来说,std::atomic<int> a = 0;执行copy initialization:

If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary (until C++17) prvalue expression (since C++17) if a converting constructor was used, is then used to direct-initialize the object.

(强调我的)

if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object

也就是说a是直接从0初始化的,没有temporary构造,也就不再是temporary 到 copy/move 来自.

在 C++17 之前,在概念上 std::atomic<int> a = 0; 需要从 0 构造临时 std::atomic,然后临时用于复制构造 a .

在C++17之前甚至允许copy elision,它被认为是一种优化:

(强调我的)

This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

这就是为什么 gcc 在 pre-c++17 模式下为 std::atomic<int> a = 0; 触发诊断。

(强调我的)

Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.

顺便说一句:我想 g++ 6.5.0-std=c++17 中有一个错误;并且在以后的版本中已经修复

Which one is correct here?

7.4.0 正确。在这种情况下可以省略副本,这就是它可以的原因。 (虽然这需要 )。

(有关详细信息,请参阅 https://en.cppreference.com/w/cpp/language/copy_initialization