aligned_storage和严格别名问题

29

我目前正在使用aligned_storage实现类似于boost :: optional的“Optional”类型。为了实现这一点,我有一个类成员如下:

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;

我使用放置new来创建对象,但我没有将返回的指针存储在任何地方。相反,在所有成员函数中,我像这样访问对象的基础类型(显然要通过一个布尔标志检查确保对象有效,该标志也存储在我的可选类型中):

T const* operator->() const {
    return static_cast<T const*>(static_cast<void const*>(&t_));
}

我的问题是这样做是否安全。我理解使用放置new会改变对象的“动态类型”,只要我继续使用该类型访问内存,就不会有问题。但是我不清楚我是否必须持有从放置new返回的指针,还是只能在需要访问时将其强制转换为底层类型。我已经阅读了C++11标准的3.10节,但我不太熟悉标准术语,无法确定。
如果可能的话,如果您在答案中提供标准参考,我会感觉更好(它可以帮助我睡得更安稳:P)。

1
我认为这取决于operator new返回的指针是否可能与生成对象的地址不同。C++03标准的第5.3.4.14节表明,如果对象是数组,则它们不一定相同,否则它们应该是相同的。 - Vaughn Cato
3
最终,关于分配的基地址是否为对象占用空间的第一个字节,这取决于具体实现。因此,最好根据new的结果来处理。显然,通过new[]进行分配时,实现通常会在分配的前几个字节中存储有关销毁数组对象所需信息的数据。 - justin
请注意,在C++17中有std::optional - Macmade
1个回答

14

ABICT使用是安全的。

  • 类型T的对象的placement new将创建一个从传递的地址开始的对象。

§5.3.4/10说:

new表达式将请求的空间大小作为std::size_t类型的第一个参数传递给分配函数。该参数不得小于正在创建的对象的大小;仅当对象是数组时,它才可能大于正在创建的对象的大小。

对于非数组对象,分配的大小不能大于对象的大小,因此对象表示必须从分配的内存的开头开始以适合。

placement new将传递的指针作为“分配”的结果返回(参见§18.6.1.3/2),因此构造对象的对象表示将从该地址开始。

  • static_cast<>T*类型与void*之间的隐式转换将在对象指针和其存储指针之间进行转换,如果对象是完整的对象。

§4.10/2说:

对象类型为T的“指向cv T”的prvalue可以转换为类型“指向cv void”的prvalue。将“指向cv T”的指针转换为“指向cv void”的结果指向对象存储的起始位置,就好像对象是类型T的最派生对象(1.8) [...]

这定义了隐式转换的语句。进一步§5.2.9[expr.static.cast]/4定义了static_cast<>用于显式转换,其中存在隐式转换以产生相同的效果:

否则,如果对于某个虚构的临时变量(8.5),声明T t(e);是合法的,则可以使用形式为static_cast<T>(e)static_cast显式将表达式e转换为类型T。经过明确转换的效果就像执行声明和初始化然后使用临时变量作为转换结果一样[...]。

对于反向static_cast<>(从void*T*),§5.2.9/13指出:

一个类型为“指向cv1 void”的prvalue可以转换为类型为“指向cv2 T”的prvalue,其中T是对象类型,cv2是与cv1相同或更大的cv限定符。[...] 将对象类型的指针值转换为“指向cv void”的指针值,再通过可能具有不同cv限定符的方式返回其原始值。

因此,如果您有一个指向 T 对象存储的 void*(这是从 T* 到对象的隐式转换结果),那么将其静态转换为 T* 将产生一个有效的指向对象的指针。

回到您的问题,前面的观点意味着,如果您有:

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
void * pvt_ = &t_;

T* pT = new (&t_) T(args...);
void * pvT = pT;

那么

  • *pT 的存储正好覆盖了 t_ 的前 size(T) 个字节的存储,以便 pvT == pvt_
  • pvt_ == static_cast<void*>(&t_)
  • static_cast<T*>(pvT) == pT
  • 综合起来得到 static_cast<T*>(static_cast<void*>(&t_)) == pT

8
"ABICT"代表什么意思? - Trevor Hickey
如果一个对象仅通过通过placement new接收到的指针进行访问,那么我认为编译器不需要关心对象的声明类型,因为不会使用该类型进行的任何访问都可能与使用新类型进行的访问别名。如果一个对象被“直接”写入作为左值,并且其地址以后通过placement new转换为另一种类型,则要求编译器执行对原始值的写入不被重新排序,以防止新类型中的写入在某些情况下影响优化。我认为,在这种情况下保证行为的好处将大于...... - supercat
...对于优化来说可能会有一些轻微的潜在障碍,但我不确定编译器的作者是否会分享这种判断。然而,对齐指令的许多价值将源自于具有静态或自动持续时间的存储器,这些存储器可以用于容纳已知最大尺寸但任意类型的物品。 - supercat
3
"As best I can tell" 的意思是“就我所知”,ABICT不是一个常用的缩写。 - Jeff Walden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接