在 C++20 之前,使用常量或引用字段在 `A` 上调用 `std::vector<A>::data()`

Ami*_*rsh 7 c++ placement-new language-lawyer c++20

这是在具有参考字段的类上放置 new的答案的后续。

调用std::vector<A>::data()上型A具有参考常量字段,将指针返回到对象可通过原始向量通过放置新的,这将导致一个的const或参考场来改变原始对象将被替换,而仍然被另一指针管理,通过调用返回data()

例如:

struct A {
    const int i = 0;
};

int main() {
    std::vector<A> vec = {{1}, {2}};
    auto ptr = vec.data();
    std::cout << ptr[1].i << std::endl; // 2
    vec.pop_back();
    vec.push_back({3}); // placement new, inside
    std::cout << ptr[1].i << std::endl; // 3
}
Run Code Online (Sandbox Code Playgroud)

C++17 试图通过引入来解决这些问题,std::launder但后来同意虽然std::launder可以解决其他问题,但它并没有真正解决上述用例的问题,如NB US042 中所述

一些问题 - 对于C++20 之前的C++ 版本:

  • 由于NB US042被接受为 C++20 规范的更改,但未标记为 DR - 是否仅根据规范建议避免在具有引用或常量字段std::vector<A>::data()的类型上使用,A如上例子?

  • 或者,规范的措辞std::vector<>::data()涵盖了这一点,使其合法并将可实现性问题留给库实现者?

  • 如果是后者,图书馆可以做些什么来使其合法

  • 如果它真的不能做任何有用的事情来使它合法,它是 C++20 之前的 UB吗?

  • 如果它是 C++20 之前的 UB,为什么这个变化不被认为是 DR 的候选者,与p0593r6相同?无论如何,编译器很可能会做正确的事情,为什么不追溯要求呢?

Ben*_*igt 1

这段代码是否有效取决于实现是否具有严格的指针安全性,这是 C++20 中消除的概念。

简单地说,在调用 后,指针ptr对于 [ ptr, ) 范围内的算术有效。ptr+2data()

在调用 后pop_back(),任何保存的指向索引 1 的指针肯定是无效的,因为迭代器失效规则std::vector<T>::pop_back()

效果:在擦除点或擦除点之后使迭代器和引用无效。

调用 后pop_back(),先前获得的指针ptr对于其原始范围内的算术不再有效(使用它进行计算ptr+1不再产生“安全派生”的指针值)。

调用后push_back(),算术使用的严格安全性ptr不会恢复。然而,在具有“宽松的指针安全性”的实现上,仍然允许使用原始的索引ptr,该索引尚未被失效pop_back()(仅减少了可达范围)。也就是说,表达式ptr+1ptr[1]涉及有效但不安全派生的指针值。

新的调用data()返回一个指针值,该值与原始值相等,但与旧保存的指针不同,它可以用于安全地导出向量当前长度的值。同样,具有宽松指针有效性的实现并不关心。


对 C++20 对具有 const 成员的对象的生命周期所做的更改在这里不起作用,因为使用现有指针来引用替换对象不仅[basic.life]pop_back.