放置 new 的返回值和其操作数的转换值之间是否存在(语义上的)差异?

16

使用placement new的返回值和其操作数的转换值之间是否存在(语义)差异?

struct Foo { ... };
char buffer[...];

Foo *a = new(buffer) Foo;
Foo *b = reinterpret_cast<Foo *>(buffer);

ab 是否存在差异?


根据DaBler的评论,这个问题告诉我们,如果使用const或引用成员: Placement new and assignment of class with const member

那么,稍微更新一下问题: 如果Foo没有const或引用成员,ab是否有任何不同?


1
new(buffer) Foo;将调用对象的构造函数;而另一个则不会。 - UKMonkey
2
@UKMonkey:抱歉,可能我表达不够清楚。两行代码都会被执行,首先是placement new,然后是cast。 - geza
是的,如果类包含const成员,则存在差异。有关详细信息,请参见此问题。 - DaBler
2个回答

22
只有 a 可以安全地直接访问通过放置新表达式创建的 Foo 对象(我们将其称为 x 以方便参考)。使用 b 需要 std::laundera 的值在 [expr.new]/1 中指定:

如果实体是非数组对象,则新表达式的结果是指向所创建对象的指针。

因此,a 的值为“指向 x 的指针”。当然,这个指针可以安全地用于访问 x

reinterpret_cast<Foo*>(buffer)将数组转换为指针并应用于buffer(请参见[expr.reinterpret.cast]/1)。转换后的结果值是“指向buffer第一个元素的指针”。 这是对象指针到不同类型对象指针的reinterpret_cast,根据[expr.reinterpret.cast]/7,其定义等同于static_cast<Foo*>(static_cast<void*>(buffer))

void*的内部转换实际上是一种隐式转换。根据[conv.ptr]/2

此转换不会改变指针值。

因此,内部转换产生的void*值为“指向buffer第一个元素的指针”。

外部转换受[expr.static.cast]/13的控制,我已将其轻微格式化为项目符号:

一个类型为“指向cv1 void的指针”的prvalue可以转换为类型为“指向cv2 T”的prvalue,其中T是对象类型,cv2是与cv1相同或更大的cv限定符。
  • 如果原始指针值表示内存中字节的地址A,并且A不满足T的对齐要求,则得到的指针值是未指定的。

  • 否则,如果原始指针值指向对象a,并且有一个类型为T(忽略cv限定符)的对象ba可互相转换,则结果是指向b的指针。

  • 否则,指针值通过转换不改变。

假设buffer已适当对齐(如果没有,那么在这一点上您会遇到麻烦),第一个项目不适用。由于这里没有pointer-interconvertiblity,因此第二个条目也不适用。因此,我们进入第三个项目——“指针值通过转换不改变”,并且仍然是“指向buffer的第一个元素的指针”。

因此,b并不指向x对象,而是指向buffer的第一个char元素,尽管它的类型是Foo*。因此,它不能用于访问x;尝试这样做会产生未定义的行为(对于非静态数据成员的情况,由于[expr.ref]中的遗漏;对于非静态成员函数的情况,由于[class.mfct.non-static]/2)。
要从b恢复指向x的指针,可以使用std::launder
b = std::launder(b); // value of b is now "pointer to x"
                     // and can be used to access x

5

a可以通过访问合法,而b不行。来源于[basic.compound]

如果两个对象ab满足以下条件,则它们是指针相互转换的:

  • 它们是同一对象,或者

  • 其中一个是标准布局联合对象,另一个是该对象的非静态数据成员,或者

  • 其中一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则是该对象的第一个基类子对象([class.mem]),或

  • 存在一个对象c,使得ac是指针相互转换的,并且cb是指针相互转换的。

如果两个对象是指针相互转换的,则它们具有相同的地址,并且可以通过reinterpret_­cast从一个指针获取到另一个指针。【注意: 数组对象及其第一个元素虽然具有相同的地址,但它们不是指针相互转换的。 --终注】

由于它们不是同一对象,也不是联合对象或子对象,因此无法进行指针相互转换。

请注意[expr.reinterpret.cast]只保证reinterpret_cast<char*>(b) == buffer

对象指针可以显式转换为不同类型的对象指针。当将对象指针类型的prvalue v 转换为对象指针类型“指向cv T的指针”时,结果为static_­cast<cv T*>(static_­cast<cv void*>(v))。【注意: 将类型为“指向T1的指针”的prvalue v转换为类型“指向T2的指针”(其中T1T2是对象类型,T2的对齐要求不严格于T1),然后再转换回原始类型,得到的是原始指针值。 --终注】


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