12

这是来自 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 (第 5 页)针对类似情况使用洗钱。

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

该问题与关于std::optional.

该问题也非常相似地适用于常量字段(即,如果上面istruct C:)const int i


编辑

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

C++17 规范 [ptr.launder] (§ 21.6.4.4): --emphasis mine --

[注:如果在同类型的现有对象占用的存储空间中创建新对象,则可以使用指向原始对象的指针来引用新对象,除非该类型包含 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

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

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


开放式问题:

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

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


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

4

2 回答 2

6

用 const 限定和引用非静态数据成员替换对象是合法的。而现在,在 C++20 中,[the 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 限定的完整对象,也不是子对象这样一个对象,并且

  • ...

于 2020-06-03T01:21:00.693 回答
1

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

第一个问题:

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

是的,是UB。这在@Language Lawyer 提到的NB 问题中明确提到:

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

第二个问题:

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

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

于 2020-06-09T22:12:42.113 回答