Как std:: frod влияет на контейнеры?

Рассмотрим следующую, упрощенную и неполную реализацию вектора фиксированного размера:

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:: washder, это означает, что поведение v[0] в строке ниже undefined.


Как предполагается, что std:: frod используется для исправления этого кода? Нужно ли начинать и заканчивать отмывание каждый раз, когда используется новое место? Текущие реализации stdlib, похоже, используют код, аналогичный тому, который был выше. Является ли поведение этих реализаций undefined?

Ответ 1

Как использовать std::launder для исправления этого кода? Нужно ли начинать и заканчивать каждый раз, когда используется новое место?

Из P0532R0 вы можете избежать необходимости вызывать launder(), если возвращаемое значение места размещения new присваивается end, Вам не нужно было бы менять указатель начала, если только вектор не был пуст, так как объект, на который указывает данный объект start, все равно будет иметь активный срок службы с кодом, который вы указали.

В той же статье указывается, что launder() является no-op, если только срок жизни объекта не закончился и был заменен новым объектом, поэтому использование launder() не приведет к штрафу за производительность, если это не нужно:

[...] тип std::launder(this) эквивалентен именно этому, как указал Ричард Смит: Помните, что launder(p) - это не-op, если p не указывает на объект, срок жизни которого закончился и где новый объект был создан в том же хранилище.

Текущие реализации stdlib, похоже, используют код, аналогичный тому, который был выше. Является ли поведение этих реализаций undefined?

Да. P0532R0 также обсуждает эту проблему, и контент похож на обсуждение в комментариях к вопросу: vector не использует размещение новых напрямую, возвращаемое значение нового вызова места размещения теряется в цепочке вызовов функций векторному распределителю, и в любом случае размещение нового используется элемент за элементом, поэтому построение внутреннего векторного механизма не может использовать возвращаемое значение в любом случае. launder() представляется инструментом, предназначенным для использования здесь. Однако тип указателя, указанный распределителем, не обязательно должен быть сырым типом указателя, а launder() работает только для необработанных указателей. Текущая реализация в настоящее время undefined для некоторых типов; launder(), похоже, не является подходящим механизмом для решения общего случая для контейнеров на основе распределителей.