这是来自C++20规范的代码示例([basic.life]/8):
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
int main() {
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C
}
以下操作是否合法或未定义行为:
struct C {
int& i; // <= the field is now a reference
void foo(const C& other) {
if ( this != &other ) {
this->~C();
new (this) C(other);
}
}
};
int main() {
int i = 3, j = 5;
C c1 {.i = i};
std::cout << c1.i << std::endl;
C c2 {.i = j};
c1.foo(c2);
std::cout << c1.i << std::endl;
}
如果这是非法的,那么使用
std::launder
会使其合法吗?应该在哪里添加?注意:p0532r0 (page 5)使用launder处理类似情况。
如果这是合法的,那么没有"指针优化障碍"(即
std::launder
)它是如何工作的?我们如何避免编译器缓存c1.i
的值?这个问题涉及到一个关于实现
std::optional
的旧ISO线程。同样地,这个问题也适用于一个常量字段(即如果在
struct C
中上面的i
是:const int i
)。
编辑
正如@Language Lawyer在下面的一个回答中指出的那样, 看起来C++20规则已经改变,响应了RU007/US042 NB comments。
C++17规范[ptr.launder](§21.6.4.4):--我强调--
[注:如果在已有对象占用的存储空间中创建了一个新对象,并且该类型的对象包含const或引用成员,则可以使用指向原始对象的指针引用新对象,否则,可以使用此函数获取可用于新对象的指针。...— end note ]
C++17 [ptr.launder]规范中的代码示例(§21.6.4.5):
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5}; // p does not point to new object (6.8) because X::n is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
C++20 [ptr.launder]规范(§ 17.6.4.5):
[注意:如果在已有对象的存储空间中创建一个新对象,可以使用指向原始对象的指针来引用新对象,除非它的完整对象是一个const对象或它是一个基类子对象;在后一种情况下,可以使用此函数来获得可用于新对象的指针。...— 结束注释]
请注意,C++17中出现的部分:
除非类型包含const或引用成员;
在C++20中被删除,并相应地更改了示例。
C++20 [ptr.launder]规范中的代码示例(§ 17.6.4.6):
struct X { int n; };
const X *p = new const X{3};
const int a = p->n;
new (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life])
// because its type is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
因此,显然在C++20中,所涉及的代码是合法的,而在C++17中,则需要在访问新对象时使用
std::launder
。
问题概述:
在C++14或之前的版本中(当
std::launder
不存在时),这种代码的情况是什么?可能是未定义行为 - 这就是为什么std::launder
被引入的原因,对吗?如果在C++20中,我们不需要
std::launder
来处理这种情况,编译器如何理解正在操作引用而没有我们的帮助(即没有"指针优化障碍")以避免缓存引用值?
类似的问题这里, 这里, 这里和这里得到了矛盾的答案,一些人认为这是有效的语法,但建议重写。我关注语法的有效性以及在不同的C++版本中是否需要std::launder
。
placement-new
(即你第一个示例中的main()
函数中的c1
变量)构造的C
对象上调用this->~C()
。并且让operator=
调用placement-new
也无法让外部代码知道他们现在需要显式地调用~C()
。所以你的代码充满了未定义行为,请不要这样做。最好让你的operator=
使用拷贝-交换习惯用法,这将允许你通过为C
定义一个适当的复制构造函数来正确处理引用成员。 - Remy Lebeauplacement-new
构造的C
对象上调用this->~C()
。”这是真的吗?我一直认为只有当类需要洗涤时(例如具有成员引用),才可能会出现问题,因为之后您必须“洗涤”对象的每个使用。 - HolyBlackCatconst int i;
也会有类似的问题 - 但是那里是否有相关的区别呢?乍一看,似乎编译器不能假设同一对象的引用成员指向相同的对象/函数,并且不能假设同一对象的const
成员在函数调用之间具有相同的值而没有可见的定义。但我想这对于优化来说可能不太好。 - aschepler