8

通过在这里工作,我的意思是std::atomic<T> a{}有效地零初始化a。我一直这么想,并且一直在实际使用它,直到这个。在解释我对此的理解之前,我想表明,至少 gcc 和 clang 在实践中是这样做的。

#include <cstring>
#include <atomic>
#include <iostream>

int main() {
  using atomic = std::atomic<int>;  
  auto p = (atomic*)operator new(sizeof(atomic));
  std::memset(p, -1, sizeof(atomic));
  new(p) atomic{};
  std::cout << p->load() << std::endl;
}

输出0gccclang上。

以下是我对为什么这应该起作用的解释(当然,您可能会不这么认为)。标准说

在以下操作定义中:

  • A 指的是原子类型之一。

[...]

A::A() noexcept = default;

效果:使原子对象处于未初始化状态。[注意:这些语义确保了与 C 的兼容性。- 尾注]

它基本上说默认构造函数是微不足道的,什么都不做。我对此表示同意,但我不明白这如何使值初始化不适用。根据cppref,值初始化的影响包括(强调我的):

如果 T 是具有既不是用户提供也不是删除的默认构造函数的类类型(即,它可能是具有隐式定义或默认默认构造函数的类),则该对象为零初始化,然后是默认值-如果它具有非平凡的默认构造函数,则初始化;

std::atomic有一个默认的默认构造函数,所以对象是

  1. 零初始化然后
  2. 如果它具有非平凡的默认构造函数,则它是默认初始化的。

第 2 点在这里不适用,因为默认的默认构造函数是微不足道的,但我没有看到任何使第 1 点无效的语句。我的理解是正确的还是我遗漏了什么?

4

1 回答 1

6

最终,值初始化案例的关键在于[dcl.init]/7的第 1 和第 2 条:

对 T 类型的对象进行值初始化意味着:

  • 如果 T 是具有用户提供的构造函数 ([class.ctor]) 的(可能是 cv 限定的)类类型(子句 [class]),则调用 T 的默认构造函数(如果 T没有可访问的默认构造函数);
  • T 是一个(可能是 cv 限定的)非联合类类型,没有用户提供的构造函数,则该对象被零初始化,并且如果 T 的隐式声明的默认构造函数不平凡,则调用该构造函数。
  • ...

应用上面两个项目符号中的哪一个取决于用户提供的 c'tor。我在对另一个答案的评论中不记得的是,= default;当应用于该答案时的复杂性。如果我们看一下[dcl.fct.def.default]/4给出的定义(强调我的):

显式默认函数和隐式声明函数统称为默认函数,实现应为它们提供隐式定义([class.ctor] [class.dtor],[class.copy]),这可能意味着将它们定义为已删除. 一个特殊的成员函数是用户提供的,如果它是用户声明的并且在其第一次声明时没有显式地默认或删除。用户提供的显式默认函数(即,在其第一次声明后显式默认)在明确默认的地方定义;如果这样的函数被隐式定义为已删除,则程序格式错误。[ 注意:在第一次声明后将函数声明为默认函数可以提供高效的执行和简洁的定义,同时为不断发展的代码库提供稳定的二进制接口。——尾注]

我们看到默认的 c'toratomic 不是用户提供的,因为它被声明为默认值,而不是被声明然后定义为默认值。所以 [dcl.init]/7 的第二个项目符号是适用的,对象是零初始化的,然后是(非)调用(平凡的默认)构造函数,它什么都不做。

于 2018-03-21T08:16:01.617 回答