在带有参考字段的类上放置新内容 [英] placement new on a class with reference field
问题描述
这是C ++ 20规范中的代码示例( [basic.life] / 8 ):
This is a code example from the C++20 spec ([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
}
以下内容是否为 合法 或 未定义行为 :
Would the following be legal or undefined behavior:
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
合法吗?
Note: p0532r0 (page 5) uses launder for a similar case.
如果它是合法,如果没有指针优化障碍 (即 std :: launder
)?我们如何避免编译器缓存 c1.i
的值?
In case it is legal, how can it work without "Pointer optimization barrier" (i.e. std::launder
)? how do we avoid the compiler from caching the value of c1.i
?
问题与旧的ISO有关有关 std :: optional
。
The question relates to an old ISO thread regarding Implementability of std::optional
.
该问题也非常类似地适用于常量字段(即,如果在结构C
中的 i
以上是: const int i
)。
The question applies also, quite similarly, to a constant field (i.e. if above i
in struct C
is: const int i
).
似乎, @Language Lawyer 指出在下面的答案中 ,该规则已在C ++ 20中更改,以响应 RU007 / US042注意事项。
C ++ 17规范[ptr.launder](第21.6.4.4节): -重点是我的-
[注意:如果在现有
对象占用的存储区中创建了新对象相同类型的指针,可以使用指向原始对象的指针
来引用新对象 ,除非该类型包含const或引用
成员; 在后一种情况下,此函数可用于获取指向新对象的
可用指针。 ...—尾注]
[ 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节):
[注意:如果在存储中创建了一个新对象,而该对象已被
对象占用同一种类型,指向原始对象的指针可以用
来引用新对象,除非其完整对象是const
对象或它是基类的子对象;在后一种情况下,此
函数可用于获取指向新对象的可用指针。
...--尾注]
[ 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 ]
注意,该部分:
除非类型包含const或引用成员;
unless the type contains const or reference members;
在C ++ 17中出现的
在C ++ 20中被删除,并且相应地更改了示例。
that appeared in C++17 was removed in C++20, and the example was changed accordingly.
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
被带到游戏中的原因,对吧?
What is the case of such code in C++14 or before (when
std::launder
didn't exist)? Probably it is UB - this is whystd::launder
was brought to the game, right?
如果在C ++ 20中对于这种情况我们不需要 std :: launder
,那么编译器如何理解在没有我们帮助的情况下对引用的操纵(即,没有指针优化障碍 )以避免缓存参考值?
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?
类似的问题此处, 此处,此处和此处有矛盾的答案,有人认为这是一种有效的语法,但建议对其进行重写。我主要关注语法的有效性以及在不同的C ++版本中 std :: launder
的需要(是或否)。
Similar questions here, here, here and here got contradicting answers, some see that as a valid syntax but advise to rewrite it. I'm focusing on the validity of the syntax and the need (yes or no) for std::launder
, in the different C++ versions.
推荐答案
回答当前未解决的问题:
To answer the currently open questions:
第一个问题:
- 在C ++ 14或更早版本中,这种代码的情况是什么(当std :: launder不存在时) )?可能是UB-这就是为什么std :: launder被带到游戏中的原因,对吧?
是的,是UB。 NB问题@Language Lawyer中明确提到了这一点:
Yes, it was UB. This is mentioned explicitly in the NB issues @Language Lawyer referred to:
由于该问题,所有标准库在广泛使用中都有未定义的行为类型。解决此问题的唯一方法是调整生命周期规则以自动清洗新的放置位置。
( https://github.com/cplusplus/nbballot/issues/7)
第二个问题:
如果在C ++ 20中对于这种情况我们不需要std :: launder,那么编译器如何才能理解在没有我们帮助的情况下操纵引用(即没有指针优化障碍),以避免缓存参考值?
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?
如果在对象之间调用了非const成员函数,编译器已经知道不会以这种方式优化对象(或子对象)的值该对象的两种用法,或者是否以该对象作为参数调用了任何函数(由by-ref传递),因为这些函数可能会更改此值。对该标准的更改仅增加了一些其他优化不合法的情况。
Compilers already know to not optimize object (or sub-object) value this way if a non-const member function was called between two usages of the object or if any function was called with the object as a parameter (passed by-ref), because this value may be changed by those functions. This change to the standard just added a few more cases where such optimization is illegal.
这篇关于在带有参考字段的类上放置新内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!