29

我目前正在使用aligned_storage 来实现类似于boost::optional 的“可选”类型。为了做到这一点,我有一个像这样的班级成员:

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

我使用placement new 来创建对象,但是我没有将返回的指针存储在任何地方。相反,我在我的所有成员函数中访问对象的底层类型(显然,通过也存储在我的 Optional 类型中的布尔标志进行检查以确保对象有效):

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

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

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

4

1 回答 1

14

ABICT 您的使用是安全的。

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

§5.3.4/10 说:

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

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

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

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

§4.10/2 说:

“指向 cv T 的指针”类型的纯右值,其中 T 是对象类型,可以转换为“指向 cv void 的指针”类型的纯右值。将“指向 cv T 的指针”转换为“指向 cv void 的指针”的结果指向类型 T 的对象所在的存储位置的开始,就好像该对象是类型 T 的最派生对象 (1.8) [...]

这将隐式转换定义为按说明进行转换。进一步的 §5.2.9[expr.static.cast]/4 定义static_cast<>了显式转换,其中存在隐式转换与隐式转换具有相同的效果:

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

对于逆static_cast<>(从void*T*),第 5.2.9/13 节规定:

“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,其中 T 是对象类型,而 cv2 与 cv1 具有相同的 cv 限定或大于 cv1 的 cv 限定。[...] 类型指针的值转换为“指向 cv void 的指针”并返回,可能具有不同的 cv 限定,应具有其原始值。

因此,如果您有一个void*指向T对象存储的指针(这是从 aT*到对象的隐式转换产生的指针值,那么 astatic_cast到 aT*将产生一个指向该对象的有效指针。

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

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正好覆盖 的存储的第一个 size(T) 字节t_,因此pvT == pvt_
  • pvt_ == static_cast<void*>(&t_)
  • static_cast<T*>(pvT) == pT
  • 合起来产生static_cast<T*>(static_cast<void*>(&t_)) == pT
于 2013-01-28T07:25:18.593 回答