【问题标题】:Swapping storage buffers containing placement new created objects交换包含放置新创建对象的存储缓冲区
【发布时间】:2015-05-21 21:05:41
【问题描述】:

我最近看到一段代码,它使用存储缓冲区创建对象,然后简单地交换缓冲区以避免复制开销。这是一个使用整数的简单示例:

std::aligned_storage_t<sizeof(int), alignof(int)> storage1;
std::aligned_storage_t<sizeof(int), alignof(int)> storage2;

new (&storage1) int(1);
new (&storage2) int(2);

std::swap(storage1, storage2);

int i1 = reinterpret_cast<int&>(storage1);
int i2 = reinterpret_cast<int&>(storage2);

//this prints 2 1
std::cout << i1 << " " << i2 << std::endl;

这在一般情况下感觉像是未定义的行为(特别是交换缓冲区,然后访问对象,就好像它们仍然存在一样),但我不确定标准对这种存储和放置 new 的使用有何规定。非常感谢任何反馈!

【问题讨论】:

  • 对于CopyConstructible 类型应该没问题。
  • 为什么不交换reinterpret_cast&lt;int&amp;&gt;(storage1)reinterpret_cast&lt;int&amp;&gt;(storage2)
  • @immibis 因为这是在原始代码中所做的。

标签: c++ c++11 language-lawyer undefined-behavior


【解决方案1】:

我怀疑有几个因素会导致这个不确定,但我们只需要一个:

[C++11: 3.8/1]: [..] T 类型对象的生命周期在以下时间结束

  • 如果 T 是具有非平凡析构函数 (12.4) 的类类型,则析构函数调用开始,或者
  • 对象占用的存储空间被重用或释放。

所有后续使用都是在报废后使用,这是不好的和错误的。

关键是每个缓冲区都被重用了。

因此,尽管我希望这至少对普通类型(和某些类)在实践中有效,但它是未定义的。


以下内容或许能救你:

[C++11: 3.8/7]: 如果在一个对象的生命周期结束之后,在该对象占用的存储空间被重用或释放之前,在原对象占用的存储位置创建一个新对象,一个指向该对象的指针原始对象、引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操作新对象[ ..]

...除了你没有创建一个新对象。

这里可能值得注意,也可能不值得注意,令人惊讶的是,随后的隐式析构函数调用都是明确定义的:

[C++11: 3.8/8]: 如果程序以静态 (3.7.1)、线程 (3.7.2) 或自动 (3.7.3) 存储持续时间结束 T 类型对象的生命周期,并且如果 T 具有一个不平凡的析构函数,程序必须确保在隐式析构函数调用发生时原始类型的对象占据相同的存储位置;否则程序的行为是不确定的。

【讨论】:

  • 我不确定第一个引号是否足以将其视为未定义的行为。具体来说,我们在交换之后不使用原始对象,而是按位复制,因此对于普通可复制类型应该没问题,但在一般情况下未定义,但这可能取决于 std::swap 的实现。如果它进行字节交换而不是创建另一个容器并在之后交换它,那么它应该总是导致未定义的行为,因为对象的生命周期在复制之前就结束了。
  • @AlexandrosLiarokapis:公平点。我不相信有任何措辞来描述一个对象的生命周期是如何从它从其他地方按位复制开始的,尽管坦率地说它非常模棱两可。也许唯一有效的答案是“没人知道;避开”!
猜你喜欢
  • 2012-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多