aligned_storage和严格的混叠

aligned_storage and strict aliasing

本文关键字:storage aligned      更新时间:2023-10-16

我目前正在使用 aligned_storage 来实现类似于 boost::Optional 的"可选"类型。为了完成这一点,我有一个这样的类成员:

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

我使用放置 new 来创建对象,但是我不将返回的指针存储在任何地方。相反,我在我的所有成员函数中访问对象的基础类型,如下所示(显然通过同样存储在我的 Optional 类型的布尔标志进行检查以确保对象有效):

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

我的问题是这是否安全。我的理解是,我对放置 new 的使用会改变对象的"动态类型",只要我继续使用该类型访问内存,我就可以了。但是,我不清楚我是否必须持有从放置 new 返回的指针,或者是否允许我在需要访问它时只转换为基础类型。我已经阅读了 C++11 标准的第 3.10 节,但是我的标准语言不够流利,无法确定。

如果可能的话,如果你能在你的答案中参考标准,我会感觉更好(它可以帮助我晚上睡觉:P)。

ABICT您的使用是安全的。

  • 放置 T 类型的对象的新位置将创建一个从传入的地址开始的对象。

§5.3.4/10 说:

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

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

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

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

§4.10/2 说:

类型为"指向 cv T 的指针"的 prvalue,其中 T 是对象类型,可以是 转换为类型为"指向 CV 无效的指针"的 PR值。结果 将"指向 cv T 的指针"转换为"指向 cv void 的指针"指向 类型 T 的对象所在的存储位置的开始,如 如果对象是 T 类型的最派生对象 (1.8) [...]

这将定义要转换的隐式转换,如前所述。此外 §5.2.9[expr.static.cast]/4 定义了显式转换的static_cast<>,其中隐式转换的存在与隐式转换具有相同的效果:

否则,可以将表达式e显式转换为类型T使用表单static_caststatic_cast<T>(e)如果声明T t(e);的格式很好,对于一些发明的临时变量t(8.5)。 这种显式转换的效果与执行 声明和初始化,然后使用临时 变量作为转换的结果。[...]

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

类型为"指向 cv1 void 的指针"的 prvalue 可以转换为 prvalue 类型为"指向 cv2 T 的指针",其中 T 是对象类型,cv2 是 简历资格与简历1相同,或比CV1更高。[...] 指向对象的指针类型的值转换为"指向 cv void 的指针" 和回来,可能具有不同的简历资格,应有其 原始值。

因此,如果有一个指向T对象的存储的void*(这是将T*隐式转换为对象的指针值),则将其static_castT*将生成指向该对象的有效指针。

回到你的问题,前面几点暗示如果你有

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_存储的第一个大小(T)字节,因此pvT == pvt_
  • pvt_ == static_cast<void*>(&t_)
  • static_cast<T*>(pvT) == pT
  • 综合起来,产生static_cast<T*>(static_cast<void*>(&t_)) == pT