Как работают std::launder и std::as_const в C++

Есть в C++ такие штуки, которые вроде как существуют в стандарте, но некоторые о них даже не задумываются, пока не наткнутся на что‑то совсем странное. Вот, например, std::launder. Что это вообще? Стирка чего‑то грязного в коде (launder)? Или std::as_const — зачем делать объект «немного более константным»?
На днях решил покопаться в этих функциях, потому что, честно говоря, они звучат интересно. Так что сегодня расскажу, что я выяснил, зачем это всё нужно, и главное — как использовать эти штуки правильно.
Зачем нужен std: launder?
Допустим, вы создаете объект с помощью placement new поверх уже существующего объекта. Вроде бы всё работает. Но где‑то глубоко в недрах кода зреет коварное неопределённое поведение (UB, как его ласково называют на Stack Overflow). Особенно если у старого объекта были const‑члены или ссылки.
Вот тут хорошо зайдет std::launder. Эта функция гарантирует, что доступ к новому объекту через старый указатель не приведёт к катастрофе.
#include
#include
struct MyClass {
const int value;
MyClass(int v) : value(v) {}
};
int main() {
alignas(MyClass) char buffer[sizeof(MyClass)];
new(buffer) MyClass(42);
MyClass* p = reinterpret_cast(buffer);
std::cout << p->value << std::endl; // Может привести к неопределённому поведению
p = std::launder(reinterpret_cast(buffer));
std::cout << p->value << std::endl; // Всё ок
return 0;
}
Вот что тут происходит: мы создаём объект MyClass в заранее выделенном буфере. Но если попытаться обратиться к его члену value через указатель p, полученный через reinterpret_cast, может произойти что угодно. Почему? Потому что C++ так решил. А вот std::launder убирает этот фокус и делает всё нормально.
std::launder возвращает указатель на объект, находящийся по тому же адресу, что и указатель p, но при этом гарантирует, что этот объект — «новый». Суть простая: он говорит компилятору, мол, «всё под контролем, можно доверять».
Если формально:
pуказывает на адресAв памяти.По адресу
Aлежит объектx.xнаходится в пределах времени своей жизни.Тип
xсовпадает сT(игнорируяconstи прочее).Тогда
std::launder(p)возвращает указатель наx. Всё просто, если закрыть глаза на тонкости.
Когда использовать std: launder?
Вот типичные ситуации:
Placement new. Вы создали новый объект поверх старого, и нужно к нему нормально обращаться.
Вы хотите перепривязать указатель к новому объекту, созданному в той же области памяти.
А что с std: as_const?
Это простая, но полезная штука. std::as_const — это функция для ленивых (или предусмотрительных). Она берёт объект и превращает его в const, не изменяя сам объект.
#include
#include
void print(const int& value) {
std::cout << value << std::endl;
}
int main() {
int x = 42;
print(std::as_const(x)); // Передаём x как const int&
return 0;
}
Тут std::as_const(x) берёт переменную x и превращает её в const int&, чтобы мы могли вызвать print.
Как это работает?
Очень просто. Определение функции выглядит вот так:
template< class T >
constexpr std::add_const_t& as_const(T& t) noexcept {
return t;
}
То есть берём неконстантную ссылку на объект t и возвращаем его как константную ссылку. Всё.
Когда использовать
Используйте, когда:
Нужно вызвать константную версию метода.
Надо передать объект в функцию, которая ожидает
const-ссылку, но сам объект менять не хочется.
Итоги
std::launder и std::as_const — это такие маленькие, но мощные инструменты. Если вам есть чем поделиться на эту тему, пишите в комментарии, интересно послушать!
По C++ в Otus пройдут следующие открытые уроки, записывайтесь, если интересно:
