【问题标题】:Does value initialization work for atomic objects?值初始化是否适用于原子对象?
【发布时间】:2018-03-21 08:00:18
【问题描述】:

这里的 work 是指std::atomic<T> a{} 有效地零初始化a。我一直这么想,并且一直在实际使用它,直到this。在解释我对此的理解之前,我想表明,至少 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;
}

gccclang 的输出均为 0

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

在以下操作定义中:

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

[...]

A::A() noexcept = default;

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

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

如果 T 是一个类类型,其默认构造函数既不是 用户提供或删除(也就是说,它可能是一个类 隐式定义或默认的默认构造函数),对象是 零初始化,然后默认初始化,如果它有 非平凡的默认构造函数;

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

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

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

【问题讨论】:

  • 在您链接到的问题中,原子变量未显式初始化,因此使用默认构造函数。它什么也不做。但是你显式地初始化你的变量,调用你描述的值初始化机制,所以你很好。
  • 所有带有新位置等的东西都只是分散注意力。
  • 好吧,你让我去验证我在 cmets 中对另一篇文章所做的假设。所以我为此道歉。你是对的。
  • @Cheersandhth.-Alf 需要放置 new 来表明零确实是值初始化的结果,而不是碰巧存在一些垃圾值。
  • 问题是在当前措辞下的std::atomic&lt;T&gt; 不需要包含T。您可以将atomic 中的字节归零,但这不足以解释它所指的T 值。这显然是一种次优状态。有一篇论文可以改变这一点。

标签: c++ c++11 language-lawyer atomic value-initialization


【解决方案1】:

最终值初始化案例的症结在[dcl.init]/7,项目符号1和2:

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

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

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

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

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

【讨论】:

  • 您的回答是对规范的合理解释(C++20 起)。然而,P0883 明确指出,对于atomic,一个空的括号初始化列表不起作用(并且默认初始化没有做任何事情,出乎意料)。因此,您的initial comment 链接到的问题是正确的(直到 C++20)。你怎么看?您如何调和这些差异?
  • 另请参阅,this answerthe other answer 讨论了相同的问题。
  • @Daniel - 我不需要调和任何差异。零初始化的对象从不意味着它是用可用的原子值初始化的(除非实现是以这种方式编写的)。 C++20 在两个方面改变了它。一方面,IIRC 用户声明的 c'tor(而不是用户提供的)将不再进行零初始化。然后是原子文件本身。
  • 当然,但是 P0883 明确表示 std::atomic&lt;int&gt; x{}; // does NOT zero initialize。它没有说// does zero initialize but the atomic might not be usable。无论如何,感谢您的回复。
  • @Daniel - 它进行零初始化......只是不一定是人们期望的零初始化。 std::atomic 很可能隐藏了一个特定于平台的 API,它返回一个原子对象的句柄。现在,您将获得一个零句柄,但不是一个零原子对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-16
  • 2010-12-06
  • 2013-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多