【问题标题】:std::is_trivially_copyable - Why are volatile scalar types not trivially copyable?std::is_trivially_copyable - 为什么 volatile 标量类型不能轻易复制?
【发布时间】:2016-07-06 00:21:28
【问题描述】:

C++17 的当前标准(我观察到 C++11 的类似措辞)对于可复制的类型的措辞非常混乱。我首先通过以下代码(GCC 5.3.0)偶然发现了这个问题:

class TrivialClass {};
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??

让混乱变得更糟,我试图查看std::is_trivial 对此事的看法,结果却更加混乱。

class TrivialClass {};
std::is_trivial<int volatile>::value; // 1 ??
std::is_trivial<TrivialClass volatile>::value; // 1

很困惑,我检查了最新的 C++17 草案,看看是否有问题,我发现了一些有点模棱两可的措辞,这可能是罪魁祸首:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73

cv 非限定标量类型、普通可复制类类型(第 9 条)、此类类型的数组以及这些类型的非易失性 const 限定版本 (3.9.3) 统称为普通可复制类型。

这里是关于可简单复制的类的信息:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.226

一般可复制的类是这样的类:

——(6.1)没有重要的复制构造函数(12.8),

——(6.2)没有非平凡的移动构造函数(12.8),

——(6.3)没有非平凡的复制赋值运算符(13.5.3、12.8),

——(6.4)没有非平凡的移动赋值运算符(13.5.3、12.8),并且

— (6.5) 有一个微不足道的析构函数 (12.4)。

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#section.12.8

构造函数:

如果不是用户提供的类 X 的复制/移动构造函数是微不足道的,它的参数类型列表等效于隐式声明的参数类型列表,并且如果

——(12.1) 类 X 没有虚函数 (10.3) 也没有虚基类 (10.1),并且

— (12.2) X 类没有 volatile 限定类型的非静态数据成员,并且

— (12.3) 选择复制/移动每个直接基类子对象的构造函数是微不足道的,并且

— (12.4) 对于 X 的每个类类型(或其数组)的非静态数据成员,选择用于复制/移动该成员的构造函数是微不足道的;

否则复制/移动构造函数是不平凡的。

作业:

类 X 的复制/移动赋值运算符如果不是用户提供的,则它是微不足道的,它的参数类型列表等效于隐式声明的参数类型列表,并且如果

— (25.1) X 类没有虚函数 (10.3) 也没有虚基类 (10.1),并且

— (25.2) X 类没有 volatile 限定类型的非静态数据成员,并且

— (25.3) 选择用于复制/移动每个直接基类子对象的赋值运算符是微不足道的,并且

— (25.4) 对于 X 的每个类类型(或其数组)的非静态数据成员,选择用于复制/移动该成员的赋值运算符是微不足道的;

否则复制/移动赋值运算符是不平凡的。

注意:更新了本节以提供更多信息。我现在认为这是 GCC 中的一个错误。然而,仅此一项并不能回答我所有的问题。

我可以看到这可能是因为 TrivialClass 没有非静态成员,因为它会通过上述规则,所以我添加了一个 int,它仍然作为可简单复制的返回。

class TrivialClass { int foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??

标准规定 volatile 应由 volatile 对象的子对象继承。意思是TrivialClass volatile 的非静态数据成员foo 现在应该是int volatile 类型。

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.76

volatile 对象是 volatile T 类型的对象、此类对象的子对象或 const volatile 对象的可变子对象

我们可以通过以下方式确认这在 GCC 中有效:

std::is_same<decltype(((TrivialClass volatile*)nullptr)->foo), int volatile>::value; // 1! (Expected)

困惑,然后我添加了一个 volatile 到 int foo 本身。它仍然通过,这显然是一个错误!

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68905#c1

class TrivialClass { int volatile foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??

继续前进,我们看到std::is_trivial 也在按预期工作:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73

标量类型、平凡类类型(第 9 条)、此类类型的数组以及这些类型的 cv 限定版本(3.9.3)统称为平凡类型。

好的,我这里有很多问题。

  • 为什么 volatile 对 is_trivially_copyable 而不是 is_trivial 很重要?
  • is_trivially_copyable 和对象类型有什么关系,是标准的错误还是问题?
  • 如果某些东西是易变的,这有什么关系?

谁能帮我解决这个问题,我真的很茫然。

【问题讨论】:

  • 请注意,标准似乎并没有说类本身必须是 cv-unqualified...但它也没有明确允许 volatile 类。
  • 但它仍然明确指出,如果存在 volatile 非静态数据成员,则对象本身不应该是可简单复制的 - 这个限定符是否不会继承到 volatile 对象的子对象?这就是为什么我想知道传递一个空对象是否有意义,但质疑为什么具有非静态数据成员的 volatile 对象也会传递。
  • 对我来说,volatile 类的含义并不是很明显。 Volatile 通常用于标量类型,以指示诸如内存映射 I/O 和其他可能在您的程序下发生变化的奇怪东西。我不知道有任何用例将整个类实例放入易失性内存中。
  • 挥发性是一个历史性的疣。甚至不推荐in kernel land。事实上,volatile 的唯一目的是禁止优化,这是永远不好的。改用锁。
  • @Dani:请参阅我链接的文章。基本上,您应该为此使用条件变量和锁。 volatile 对优化有害。另请参阅this excellent post on the topic

标签: c++ c++11 standards volatile typetraits


【解决方案1】:

显然这是修复标准缺陷的方式,但你不是唯一对此感到困惑的人。

来自http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2094

  1. 具有 volatile 成员的类的简单复制/移动构造函数

章节:12.8 [class.copy] 状态:开放 提交者:Daveed Vandevoorde 日期:2015-03-06

问题 496 的解决方案包括 添加 12.8 [class.copy] 第 25.2 段,使类的 如果它具有非静态数据成员,则复制/移动构造函数非平凡 具有 volatile 限定的类型。此更改破坏了 IA-64 ABI,因此它 已要求 CWG 重新考虑该决议的这一方面。

在相关说明中,问题 496 的解决方案也更改为 3.9 [basic.types] 第 9 段,它使 volatile 限定的标量类型 “微不足道”,但不是“微不足道的可复制”。目前尚不清楚为什么会有 这里的区别;中唯一实际使用“平凡类型” 标准似乎在 qsort 的描述中,应该 可能使用“平凡可复制”。 (另见 issue 1746。)

来自问题描述(从 30.12.2004 开始):

  1. volatile 限定类型真的是 POD 吗? :

但是在 3.9 [basic.types] 第 3 段中,标准明确了 POD 可以被复制“好像”它们是字节的集合 内存:

对于任何 POD 类型 T,如果指向 T 的两个指针指向不同的 T 对象 obj1 和 obj2,其中 obj1 和 obj2 都不是基类子对象, 如果 obj1 的值被复制到 obj2,使用 std::memcpy 库函数,obj2 随后应与 obj1 保持相同的值。 这样做的问题是 volatile 限定类型可能需要 以特定方式复制(通过仅使用原子操作复制 多线程平台,例如),以避免“内存 逐字节复制时可能会发生撕裂。

我意识到标准对 volatile 合格的说明很少 类型,并且(还)没有关于多线程平台,但是 尽管如此,这是一个真正的问题,原因如下:

即将推出的 TR1 将定义一系列特征,提供有关类型属性的信息,包括类型是否为 POD 和/或具有微不足道的构造/复制/分配操作。库可以使用此信息来优化其代码,例如,如果 T 是 POD,则可以使用 memcpy 复制类型 T 的数组,而不是逐个元素的副本。这是 TR1 类型特征章节背后的主要动机之一。然而,在这些情况下应该如何处理 volatile 类型(或具有 volatile 类型作为成员的 POD)尚不清楚。2005 年 4 月会议的笔记:

尚不清楚 volatile 限定符是否真的以这种方式保证了原子性。此外,Evolution 工作组正在进行的多线程内存模型工作似乎在这一点上为易失性数据指定了额外的语义,在解决这个问题之前需要考虑这项工作。

【讨论】:

  • 接受这个作为答案。不幸的是,当不允许此类类型易失时,没有给出很好的理由说明为什么 IA64 ABI 兼容性会中断。我确信这是有充分理由的,它只是让标准做一些我绝对不会想到的事情。但是,无论如何,这听起来很空洞。 volatile 只是 C++ 的一个奇怪的特例。
  • 哎呀,之前错过了很大一部分答案,对此感到抱歉(现在添加)。显然,似乎不清楚 volatile 与 atomic 的关系是什么
猜你喜欢
  • 2021-02-05
  • 2017-05-11
  • 1970-01-01
  • 2016-02-14
  • 2012-05-07
  • 1970-01-01
  • 2018-10-29
  • 2012-06-27
  • 2014-01-26
相关资源
最近更新 更多