在具有参考字段的 class 上的新位置
placement new on a class with reference field
这是来自 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) 在类似情况下使用洗衣店。
万一合法,没有"Pointer optimization barrier"(即std::launder
)如何工作?我们如何避免编译器缓存 c1.i
?
的值
该问题与有关 Implementability of std::optional
的旧 ISO 线程有关。
这个问题同样适用于常量字段(即如果struct C
中的i
以上是:const int i
) .
编辑
看来,正如@语言律师所指出的那样 , that the rules have been changed in C++20, in response to RU007/US042 NB comments。
C++17 规范 [ptr.launder] (§ 21.6.4.4): --强调我的--
[ Note: If a new object is created in storage occupied by an existing
object of the same type, a pointer to the original object can be used
to refer to the new object unless the type contains const or reference
members; in the latter cases, this function can be used to obtain a
usable pointer to the new object. ...— 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):
[ Note: If a new object is created in storage occupied by an existing
object of the same type, a pointer to the original object can be used
to refer to the new object unless its complete object is a const
object or it is a base class subobject; in the latter cases, this
function can be used to obtain a usable pointer to the new object.
...— end note ]
注意那部分:
unless the type contains const or reference members;
在 C++17 中出现的在 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
不存在时)这样的代码是什么情况?可能是 UB - 这就是 std::launder
被带到游戏中的原因,对吧?
如果在 C++20 中我们不需要 std::launder
这种情况,编译器如何理解引用在没有我们帮助的情况下被操纵(即没有 "Pointer optimization barrier") 以避免引用值的缓存?
类似的问题 here, here, here and 得到了相互矛盾的答案,有些人认为这是一种有效的语法,但建议重写它。我专注于 std::launder
在不同 C++ 版本中的语法有效性和需求(是或否)。
用 const 限定和引用非静态数据成员替换对象是合法的。而现在,在 C++20 中,原始对象的[名称|a [指针|引用]] 将在替换后引用新对象。规则已更改以响应 RU007/US042 NB 评论 http://wg21.link/p1971r0#RU007:
RU007. [basic.life].8.3 Relax pointer value/aliasing rules
...
Change 6.7.3 [basic.life] bullet 8.3 as follows:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
...
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type neither a complete object that is const-qualified nor a subobject of such an object, and
...
回答当前开放的问题:
第一个问题:
- What is the case of such code in C++14 or before (when std::launder didn't exist)? Probably it is UB - this is why std::launder was brought to the game, right?
是的,是 UB。这个在@Language Lawyer 提到的NB issues中有明确提到:
Because of that issue all the standard libraries have undefined behaviors in widely used types. The only way to fix that issue is to adjust the lifetime rules to auto-launder the placement new.
(https://github.com/cplusplus/nbballot/issues/7)
第二个问题:
If in C++20 we do not need std::launder for such a case, how the compiler can understand that the reference is being manipulated without our help (i.e. without "Pointer optimization barrier") to avoid caching of the reference value?
如果在对象的两次使用之间调用了非常量成员函数,或者如果使用对象作为参数调用了任何函数(通过by-ref),因为这个值可能会被那些函数改变。对标准的这一更改只是增加了一些此类优化是非法的情况。
这是来自 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) 在类似情况下使用洗衣店。
万一合法,没有"Pointer optimization barrier"(即std::launder
)如何工作?我们如何避免编译器缓存 c1.i
?
该问题与有关 Implementability of std::optional
的旧 ISO 线程有关。
这个问题同样适用于常量字段(即如果struct C
中的i
以上是:const int i
) .
编辑
看来,正如@语言律师所指出的那样
C++17 规范 [ptr.launder] (§ 21.6.4.4): --强调我的--
[ Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless the type contains const or reference members; in the latter cases, this function can be used to obtain a usable pointer to the new object. ...— 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):
[ Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless its complete object is a const object or it is a base class subobject; in the latter cases, this function can be used to obtain a usable pointer to the new object. ...— end note ]
注意那部分:
unless the type contains const or reference members;
在 C++17 中出现的在 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
不存在时)这样的代码是什么情况?可能是 UB - 这就是std::launder
被带到游戏中的原因,对吧?如果在 C++20 中我们不需要
std::launder
这种情况,编译器如何理解引用在没有我们帮助的情况下被操纵(即没有 "Pointer optimization barrier") 以避免引用值的缓存?
类似的问题 here, here, here and std::launder
在不同 C++ 版本中的语法有效性和需求(是或否)。
用 const 限定和引用非静态数据成员替换对象是合法的。而现在,在 C++20 中,原始对象的[名称|a [指针|引用]] 将在替换后引用新对象。规则已更改以响应 RU007/US042 NB 评论 http://wg21.link/p1971r0#RU007:
RU007. [basic.life].8.3 Relax pointer value/aliasing rules
...
Change 6.7.3 [basic.life] bullet 8.3 as follows:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
...
the
type of theoriginal object isnot const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference typeneither a complete object that is const-qualified nor a subobject of such an object, and...
回答当前开放的问题:
第一个问题:
- What is the case of such code in C++14 or before (when std::launder didn't exist)? Probably it is UB - this is why std::launder was brought to the game, right?
是的,是 UB。这个在@Language Lawyer 提到的NB issues中有明确提到:
Because of that issue all the standard libraries have undefined behaviors in widely used types. The only way to fix that issue is to adjust the lifetime rules to auto-launder the placement new. (https://github.com/cplusplus/nbballot/issues/7)
第二个问题:
If in C++20 we do not need std::launder for such a case, how the compiler can understand that the reference is being manipulated without our help (i.e. without "Pointer optimization barrier") to avoid caching of the reference value?
如果在对象的两次使用之间调用了非常量成员函数,或者如果使用对象作为参数调用了任何函数(通过by-ref),因为这个值可能会被那些函数改变。对标准的这一更改只是增加了一些此类优化是非法的情况。