5

[1]

有没有必要将p0593r6 添加到 C++20(第 6.7.2.11 节对象模型[intro.object])中std::launder,而在 C++17 中需要相同的用例std::launder,或者它们完全正交


[2]

[ptr::launder]规范中的示例是:

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

@Nicol Bolas在此 SO answer中给出了另一个示例,使用指向有效存储但类型不同的指针:

aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

是否有其他用例,与允许投射两个不可透明替换的对象无关,用于使用std::launder

具体来说:

  • 从 A* 到 B* 的reinterpret_cast是否都是指针互转换的std::launder,在任何情况下都可能需要使用?(即两个指针可以指针互转换但不能透明地替换吗?规范在这两个术语之间没有关联)。
  • void*到 T* 的reinterpret_cast是否需要使用?std::launder
  • 下面的代码是否需要使用std::launder?如果是这样,在规范中的哪种情况下需要这样做?

受此讨论启发的具有引用成员的结构:

struct A {
    constexpr A(int &x) : ref(x) {}
    int &ref;
};

int main() {
    int n1 = 1, n2 = 2;
    A a { n1 };
    a.~A();
    new (&a) A {n2};
    a.ref = 3; // do we need to launder somebody here?
    std::cout << a.ref << ' ' << n1 << ' ' << n2 << std::endl;
}
4

1 回答 1

3

在 C++17 之前,具有给定地址和类型的指针始终指向位于该地址的该类型的对象,前提是代码遵守 [basic.life] 的规则。(请参阅:自 C++17 以来,具有正确地址和类型的指针是否仍然始终是有效指针?)。

但在 C++17 标准中,为指针值添加了新特性。这种质量不是在指针类型中编码,而是直接限定值,与类型无关(这也是可追溯性的情况)。它在[basic.compound]/3中有描述

指针类型的每个值都是以下之一:

  • 指向对象或函数的指针(该指针被称为指向对象或函数),或

  • 超过对象末尾的指针 ([expr.add]),或

  • 该类型的空指针值,或无效的指针值。

指针值的这种性质有其自己的语义(转换规则),对于reinterpret_cast它的情况在下一段中描述:

如果两个对象是指针可互转换的,那么它们具有相同的地址,并且可以通过 reinterpret_cast从指向另一个的指针获得指向其中一个的指针。

在 [basic-life] 中,我们可以找到另一条规则,它描述了在重用对象存储时如何转换这种质量:

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

如您所见,质量“指向对象的指针”附加到特定对象。

这意味着在您给出的第一个示例的以下变体中,reinterpret_cast不允许我们不使用指针优化屏障:

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 = *reinterpret_cast <int*> (p);                 // undefined behavior
const int c = *std::launder(reinterpret_cast <int*> (p)); 

Areinterpret_cast不是指针优化屏障:reinterpret_cast <int*>(p)指向被销毁对象的成员。

reinterpret_cast设想它的另一种方式是,只要对象是指针可相互转换的,或者如果它被强制转换为 void 然后返回到指针可相互转换的类型,“指向”的质量就会保持不变。(见[exp.static_cast]/13)。所以reinterpret_cast <int*>(reinterpret_cast <void*>(p))仍然指向被破坏的对象。

对于您给出的最后一个示例,该名称a指的是一个非 const 完整对象,因此原始对象a可以透明地被新对象替换。


对于您问的第一个问题:“是否存在将 p0593r6 添加到 C++20(第 6.7.2.11 节对象模型 [intro.object])使得 std::launder 没有必要的任何情况,其中相同的用例在C++17 需要 std::launder,还是它们完全正交?”

老实说,我找不到任何 std::launder 可以补偿隐含生命周期对象的情况。但我发现一个例子是隐式生命周期对象使 std::launder 有用:

  class my_buffer {
      alignas(int) std::byte buffer [2*sizeof(int)];
      
      int * begin(){
         //implictly created array of int inside the buffer
         //nevertheless to get a pointer to this array, 
         //std::launder is necessary as the buffer is not
         //pointer inconvertible with that array
         return *std::launder (reinterpret_cast <int(*)[2]>(&buffer));
         }
      create_int(std::size_t index, int value){
         new (begin()+index) auto{value};
         }
       };
      
于 2020-05-31T13:04:15.767 回答