【问题标题】:How does std::launder affect containers?std::launder 如何影响容器?
【发布时间】:2016-10-20 21:40:42
【问题描述】:

考虑以下固定大小向量的简化和不完整实现:​​

template<typename T>
class Vec {
  T *start, *end;

public:
  T& operator[](ssize_t idx) { return start[idx]; }

  void pop() {
    end--;
    end->~T();
  }

  template<typename... U>
  void push(U... args) {
    new (end) T { std::forward<U>(args)... };
    end++;
  }
};

现在考虑以下 T:

struct T {
  const int i;
};

还有以下用例:

Vec<T> v;
v.push(1);
std::cout << v[0].i;
v.pop();
v.push(2);
std::cout << v[0].i;

索引运算符使用start 指针访问对象。此时的对象被pop 销毁,另一个对象由push(2) 在其存储位置创建。如果我正确阅读了有关std::launder 的文档,这意味着下面一行中v[0] 的行为是未定义的。


应该如何使用 std::launder 来更正此代码?每次使用新安置时,我们是否必须清洗开始和结束?标准库的当前实现似乎使用与上面发布的代码类似的代码。这些实现的行为是否未定义?

【问题讨论】:

  • std::vector 自 C++03 以来一直无法在标准 C++ 中实现。
  • @T.C.:愿意扩展一下吗?鉴于它,你知道,
  • @LightnessRacesinOrbit data() 的返回值(或在 C++03 中,&amp;v[0])需要允许对其进行指针运算,并且指针运算仅针对指向同一数组的指针定义. vector,由于显而易见的原因,实际上不能在内部使用数组。
  • @TC:(试图确认我对您的意思的理解)假设您不将动态分配的“数组”块算作“数组”,那么您也相信我们不能合法地在 new int[N]? 上执行指针运算?
  • @LightnessRacesinOrbit vector 没有 new a T[N]。它分配一个存储块并将元素一个接一个地构造到该存储中。

标签: c++ undefined-behavior c++17


【解决方案1】:

std::launder 应该如何用于更正此代码?每次使用placement new 时我们都必须清洗start 和end 吗?

P0532R0 开始,如果将placement new 的返回值分配给end,则可以避免调用launder()。除非向量为空,否则您无需更改起始指针,因为 start 当前指向的对象在您提供的代码中仍具有活动生命周期。

同一篇论文指出launder() 是无操作的,除非对象生命周期已结束并已被新对象替换,因此在不必要的情况下使用launder() 不会导致性能损失:

[...] std::launder(this) 的类型与 Richard Smith 指出的一样:请记住,launder(p) 是无操作的,除非 p 指向一个生命周期已结束的对象,并且其中有一个新对象已在同一存储中创建。

stdlib 的当前实现似乎使用与上面发布的代码类似的代码。这些实现的行为是否未定义?

是的。 P0532R0也讨论了这个问题,内容和问题cmets中的讨论类似:vector不直接使用placement new,placement new调用的返回值丢失在对vector的allocator的函数调用链中,并且在任何情况下放置 new 都是逐个元素使用的,因此构造内部向量机器无论如何都不能使用返回值。 launder() 似乎是打算在这里使用的工具。但是,分配器指定的指针类型根本不需要是原始指针类型,launder() 仅适用于原始指针。某些类型的当前实现当前未定义; launder() 似乎不是解决基于分配器的容器的通用情况的合适机制。

【讨论】:

    猜你喜欢
    • 2020-07-10
    • 1970-01-01
    • 2019-06-13
    • 2019-12-19
    • 2020-09-18
    • 2018-08-20
    • 1970-01-01
    • 2021-10-22
    • 2018-12-14
    相关资源
    最近更新 更多