std::is_trivially_copyable - 为什么易失性标量类型不可简单复制?

2024-05-06

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 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 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 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 限定类型的非静态数据成员, and

— (12.3) 选择复制/移动每个直接基类子对象的构造函数很简单,并且

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

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

任务:

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

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

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

— (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 ??

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

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

易失性对象是 易失性 T 类型的对象、此类对象的子对象或 const 易失性对象的可变子对象

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

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

很困惑,然后我添加了一个 volatile 到int foo本身。还是过去了,这明显是bug啊!

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68905#c1 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 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 和对象类型有什么关系,是错误还是标准问题?
  • 无论如何,如果某些东西是不稳定的,那为什么很重要呢?

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


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

From https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2094 https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2094:

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

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

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

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

根据问题描述(2004年12月30日起):

  1. 易失性限定类型真的是 POD 吗? :

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

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

我意识到该标准很少提到挥发性合格 类型,并且对多线程平台一无所知,但是 尽管如此,这是一个真正的问题,原因如下:

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

std::is_trivially_copyable - 为什么易失性标量类型不可简单复制? 的相关文章

随机推荐