【问题标题】:Member initialization for non-copyable variable in C++17C++17中不可复制变量的成员初始化
【发布时间】:2020-01-29 12:56:33
【问题描述】:

在对不可复制变量(如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++ initialization language-lawyer c++17 copy-elision


【解决方案1】:

自 C++17 以来行为发生了变化,这要求编译器省略 std::atomic&lt;int&gt; a = 0; 中的复制/移动构造,即 guaranteed copy elision

(强调我的)

在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。对象直接构建到存储中,否则它们将被复制/移动到。 复制/移动构造函数不需要存在或可访问,因为语言规则确保不会发生复制/移动操作,即使在概念上也是如此

具体来说std::atomic&lt;int&gt; a = 0;执行copy initialization

如果 T 是类类型,并且 other 的类型的 cv 非限定版本不是 T 或派生自 T,或者如果 T 是非类类型,但 other 的类型是类类型,用户-检查定义的转换序列,这些转换序列可以从 other 的类型转换为 T(如果 T 是类类型并且转换函数可用,则转换为从 T 派生的类型),并通过重载决议选择最佳的转换序列。转换的结果,如果使用了转换构造函数,则为 prvalue temporary (until C++17) prvalue expression (since C++17),然后用于直接初始化对象。

(强调我的)

如果 T 是类类型并且初始化程序是纯右值表达式,其 cv 非限定类型与 T 相同,初始化程序表达式本身,而不是从它的临时物化,用于初始化目标对象

这意味着a 直接从0 初始化,没有要构造的临时,然后不再是要复制/移动的临时。 p>

在 C++17 之前,概念上std::atomic&lt;int&gt; a = 0; 需要从0 构造一个临时的std::atomic,然后使用该临时来复制构造a

即使copy elision 在 C++17 之前也是允许的,它被认为是一种优化:

(强调我的)

这是一个优化:即使它发生并且没有调用 copy/move (since C++11) 构造函数,它仍然必须存在并且可以访问(好像根本没有优化),否则程序会出错-形成

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

(强调我的)

注意:上面的规则没有指定优化:C++17 prvalues 和 temporaries 的核心语言规范 与早期 C++ 修订版的基本不同:不再有临时复制/移动。描述 C++17 机制的另一种方式是“未实现的值传递”:prvalues 被返回并使用,而不会实现临时的

顺便说一句:我想g++ 6.5.0-std=c++17 中存在错误;并已在后续版本中修复。

【讨论】:

  • 我认为您以某种方式误解了 OP 的要求。 OP 不是要求 c++17 与 pre-c++17 相比。
  • @darune 我认为 OP 在询问为什么复制初始化适用于 -std=c++17(但不适用于 -std=c++11-std=c++14,我试图解释为什么它应该适用于 C++17。
  • 没错,我认为这个答案比@darune 的另一个答案更正确(就目前而言)
  • @MaxLanghof 除了 gcc 6.5.0 和 -std=c++17 不接受该代码时。但这仅仅是因为它的 C++17 支持仍然是部分的,并且还没有实现强制复制省略。
  • 不同的 cpp 标准和 g++ 版本导致我在这里感到困惑。当问这个问题时,我不知道 C++17 强制执行此省略,也不知道旧 g++ 有错误。感谢大家的回答/cmets!
【解决方案2】:

这里哪一个是正确的?

7.4.0 是正确的。对于这种情况,可以省略副本,这就是为什么可以。 (虽然这需要)。

(详情请参阅https://en.cppreference.com/w/cpp/language/copy_initialization

【讨论】:

  • 规则不是类似于:即使可以省略,行为也应该好像没有执行省略,并且应该是合法的。如果编译器可以弄清楚,那么就会发生省略。 (对不起,我现在手头没有标准)。所以也许不是它被忽略了,而是 C++17 的一些规则更改(围绕它)导致它在那里成功?
  • @ustulation 这可能只是早期版本中的一个错误(不幸的是,这些类型的 c++ 实现中的错误有些普遍) - 请参阅en.cppreference.com/w/cpp/language/copy_initialization
  • 看到这个:en.cppreference.com/w/cpp/language/copy_elision:“这是一个优化:即使它发生并且复制/移动(C++ 11 起)构造函数没有被调用,它仍然必须存在并且可以访问(就好像根本没有优化一样),否则程序是非良构的:"
  • @ustulation 在我提供的复制初始化链接中也有说明
  • @ustulation 确实是 C++17 中的一个变化使得这个格式良好(见另一个答案):而不是一个可以被省略的临时的概念副本(或移动) ,它现在只是初始化,没有临时物化(甚至在概念上)。
猜你喜欢
  • 2012-08-23
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-17
相关资源
最近更新 更多