Какова цель std::launder?

P0137 представляет шаблон функции std::launder и вносит много, много изменений в стандарт в разделах, касающихся союзов, времени жизни и указателей.

Какую проблему решает эта статья? Какие изменения в языке, которые я должен знать? И что мы launderING?

3 ответа

Решение

std::launder удачно назван, но только если вы знаете, для чего он нужен. Он выполняет отмывание памяти.

Рассмотрим пример в статье:

struct X { const int n; };
union U { X x; float f; };
...

U u = {{ 1 }};

Этот оператор выполняет агрегатную инициализацию, инициализируя первый член U с {1},

Так как n это const переменная, компилятор может предположить, что u.x.n всегда должно быть 1.

Так что же произойдет, если мы сделаем это:

X *p = new (&u.x) X {2};

Так как X тривиально, нам не нужно уничтожать старый объект перед созданием нового на его месте, так что это совершенно законный код. Новый объект будет иметь n член быть 2.

Так скажи мне... что будет u.x.n вернуть?

Очевидный ответ будет 2. Но это не так, потому что компилятор может предположить, что действительно const переменная (не просто const&, но объявлена переменная объектаconst) никогда не изменится. Но мы просто изменили это.

[basic.life] / 8 разъясняет обстоятельства, когда можно получить доступ к вновь созданному объекту через переменные / указатели / ссылки на старый. И имея const член является одним из дисквалифицирующих факторов.

Итак... как мы можем говорить о u.x.n должным образом?

Мы должны отмыть нашу память:

assert(*std::launder(&u.x.n) == 2); //Will be true.

Отмывание денег используется для предотвращения отслеживания людьми, откуда вы взяли свои деньги. Отмывание памяти используется для предотвращения отслеживания компилятором того, откуда вы получили ваш объект, что вынуждает его избегать любых оптимизаций, которые могут больше не применяться.

Еще один дисквалифицирующий фактор заключается в том, что вы меняете тип объекта. std::launder может помочь и здесь:

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

[basic.life] / 8 говорит нам, что если вы выделите новый объект в хранилище старого, вы не сможете получить доступ к новому объекту через указатели на старый. launder позволяет нам обойти это.

Я думаю, что есть две цели .

  1. Барьер дляпостоянное сворачивание/распространение, в том числедевиртуализация.
  2. Барьер для детального анализа псевдонимов на основе объектной структуры.

Барьер для чрезмерно агрессивного постоянного фолда/распространения (заброшен)

Исторически сложилось так, что стандарт C++ позволял компиляторам предполагать, что значение константного или ссылочного нестатического члена данных, полученное каким-либо образом, является неизменяемым, даже если содержащий его объект не является константным и может быть повторно использован путем размещения нового.

В C++17/ представлен как функционал, который отключает вышеупомянутую (неправильную) оптимизацию (CWG 1776), которая необходима для . Как обсуждалось в P0532R0, переносные реализации а также также могут понадобиться , даже если они являются компонентами C++98.

К счастью, такая (неправильная) оптимизация запрещена RU007 (входит в состав P1971R0 и C++20). Насколько я знаю, нет компилятора, выполняющего эту (неправильную) оптимизацию.

Барьер для девиртуализации

Указатель виртуальной таблицы (vptr) можно считать постоянным в течение всего времени жизни содержащего его полиморфного объекта, что необходимо для девиртуализации. Учитывая, что vptr не является нестатическим элементом данных, компиляторы по-прежнему могут выполнять девиртуализацию на основе предположения, что vptr не изменяется (т. е. либо объект все еще находится в своем жизненном цикле, либо он повторно используется новым объектом того же динамического типа) в некоторых случаях.

Для некоторых необычных применений, которые заменяют полиморфный объект новым объектом другого динамического типа (показан здесь), необходим в качестве барьера для девиртуализации.

Реализован IIUC Clang ( ) с этой семантикой (LLVM-D40218).

Барьер для анализа псевдонимов на основе объектной структуры

P0137R1P0137R1 также изменяет объектную модель C++, вводя взаимопреобразуемость указателей. Такое изменение IIUC позволяет использовать некоторый «анализ псевдонимов на основе структуры объекта», предложенный в N4303 .

В результате P0137R1 напрямую использует разыменование указатель из массив не определен, даже если массив обеспечивает хранилище для другого объекта правильного типа. А затем нужен для доступа к вложенному объекту.

Этот вид анализа псевдонимов кажется чрезмерно агрессивным и может нарушить многие полезные основы кода. AFAIK в настоящее время не реализован ни одним компилятором.

Отношение к анализу псевдонимов на основе типов / строгому псевдониму

IIUC и анализ псевдонимов на основе типов / строгое псевдонимы не связаны. требует, чтобы по указанному адресу находился живой объект правильного типа.

Однако кажется, что они случайно связаны в Clang (LLVM-D47607).

неверный номер. Эта функция выполняет противоположность отмыванию: она загрязняет указанную память, чтобы удалить любые ожидания, которые компилятор мог иметь в отношении указанного значения. Это исключает любую оптимизацию компилятора, основанную на таких ожиданиях.

Таким образом, в ответе @NicolBolas компилятор может предположить, что некоторая память хранит некоторое постоянное значение; или не инициализирован. Вы говорите компилятору: «Это место (сейчас) загрязнено, не делайте этого предположения».

Если вам интересно, почему компилятор всегда будет придерживаться своих наивных ожиданий, и вам нужно, чтобы вы заметно испортили это - вы можете прочитать это обсуждение:

Зачем вводить std::launder вместо того, чтобы об этом позаботился компилятор?

... которые подводят меня к такому представлению о том, что std::launder означает.

Другие вопросы по тегам