在具有参考字段的类上放置新的

Ami*_*rsh 11 c++ language-lawyer c++20 stdlaunder

这是来自 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
}
Run Code Online (Sandbox Code Playgroud)

以下是合法的还是未定义的行为

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;
}
Run Code Online (Sandbox Code Playgroud)

如果是非法的,会std::launder使其合法吗?应该在哪里添加?

注意: p0532r0 (第 5 页)在类似情况下使用了 launder。

如果它是合法的,它如何在没有“指针优化障碍”(即std::launder)的情况下工作?我们如何避免编译器缓存 的值c1.i

这个问题涉及到关于一个老ISO螺纹的可实现性std::optional

问题也适用,非常类似,一个常数字段(即,如果上述istruct C是:const int i)。


编辑

正如@Language Lawyer在下面的回答中指出的那样似乎C++20 中的规则已更改,以响应RU007/US042 NB 评论

C++17 规范 [ptr.launder](第 21.6.4.4 节):-- 强调我的--

[注意:如果新对象是在同类型的现有对象占用的存储中创建的,除非该类型包含const或引用成员,否则可以使用指向原始对象的指针来引用新对象在后一种情况下,此函数可用于获取指向新对象的可用指针。...- 尾注]

规范中的 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
Run Code Online (Sandbox Code Playgroud)

C++20 [ptr.launder] 规范(第 17.6.4.5 节):

[注意:如果在存储中创建了一个新对象,该对象被同类型的现有对象占用,则可以使用指向原始对象的指针来引用新对象,除非其完整对象是const对象或基类子对象;在后一种情况下,此函数可用于获取指向新对象的可用指针。...- 尾注]

请注意该部分:

除非类型包含 const 或引用成员;

出现在 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
Run Code Online (Sandbox Code Playgroud)

因此,显然有问题的代码在 C++20 中是合法的,而在 C++17 中,它需要std::launder在访问新对象时使用。


开放问题:

  • 在 C++14 或之前(当std::launder不存在时)这样的代码是什么情况?可能是UB - 这就是为什么std::launder被带到游戏中,对吧?

  • 如果在 C++20 中我们不需要std::launder这种情况,编译器如何理解在没有我们帮助(即没有“指针优化屏障”)的情况下操纵引用以避免缓存引用值?


类似的问题herehereherehere得到了矛盾的答案,有些人认为这是一种有效的语法,但建议重写它。我专注于std::launder不同 C++ 版本中语法的有效性和 , 的需求(是或否)。

Lan*_*yer 5

用 const 限定和引用非静态数据成员替换对象是合法的。而现在,在 C++20 中,[name of|a [pointer|reference] to] 原始对象将引用替换后的新对象。规则已更改以响应 RU007/US042 NB 评论http://wg21.link/p1971r0#RU007

RU007。[basic.life].8.3 放宽指针值/别名规则

...

将 6.7.3 [basic.life] 8.3 修改如下:

如果在一个对象的生命周期结束后,在该对象所占用的存储空间被重用或释放之前,在原对象所占用的存储位置创建一个新对象,一个指向原对象的指针,一个指向该对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:

  • ...

  • 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含任何类型为 const 限定的非静态数据成员或引用类型, 既不是具有 const 限定的完整对象,也不是子对象这样一个对象,和

  • ...


Yeh*_* B. 1

回答当前悬而未决的问题:

第一个问题:

  • C++14 或之前的此类代码(当 std::launder 不存在时)的情况如何?可能是 UB - 这就是为什么 std::launder 被引入游戏的原因,对吧?

是的,就是UB。@Language Lawyer 提到的 NB 问题中明确提到了这一点:

由于这个问题,所有标准库在广泛使用的类型中都有未定义的行为。解决该问题的唯一方法是调整生命周期规则以自动清洗新的展示位置。(https://github.com/cplusplus/nbballot/issues/7

第二个问题:

如果在 C++20 中我们不需要 std::launder 来处理这种情况,那么编译器如何理解在没有我们的帮助(即没有“指针优化屏障”)的情况下正在操作引用以避免缓存引用值?

如果在对象的两次使用之间调用非常量成员函数,或者使用该对象作为参数(通过引用传递)调用任何函数,编译器已经知道不要以这种方式优化对象(或子对象)值,因为这个值可能会被那些函数改变。对标准的这一更改只是增加了一些此类优化非法的情况。