- PVSM.RU - https://www.pvsm.ru -
Каждый раз, когда мы пишем класс, управляющий ресурсами, мы задумываемся о том, что, скорее всего, для него придётся писать move-конструктор и move-присваивание. Ведь иначе объекты такого типа становятся неуклюжими, как std::mutex
, ими тяжело пользоваться на практике: ни вернуть из функции, ни передать в функцию по значению, ни положить в вектор — а если положить его в другой класс как один из членов, то тот класс также «заболевает».
Положим, мы преодолели свою лень (хотя в Rust таких проблем нет!) и садимся писать move-операции для нашего класса. Проблема в том, что move-семантика в C++ имеет фундаментальное ограничение: каждый владеющий ресурсами тип с move-операциями должен иметь пустое состояние, то есть состояние с украденными ресурсами. Его нужно описывать в документации и предоставлять ему поддержку, то есть тратить время и силы на то, что нам не нужно.
Для абстрактных типов данных пустое состояние обычно бессмысленно — если у объекта украли его ресурсы, то он не сможет выполнять свои обычные функции. Но мы вынуждены это делать, чтобы реализовать move-семантику. Для некоторых типов пустое состояние недопустимо: open_file
(в противовес теоретическому file
), not_null_unique_ptr<T>
(в противовес unique_ptr<T>
).
Говоря словами [1] Arthur O'Dwyer, мы заказывали телепорт, а нам дали «вас клонируют и убивают первоначальную копию». Чтобы вернуть себе телепорт, проходите под кат!
Я опишу несколько предложений к стандарту C++, которые объединены одной темой: свести к минимуму число перемещений. Но для начала, ещё раз: почему меня должно это заботить?
Итак, поехали.
Это предложение [4] к стандарту, за авторством Arthur O'Dwyer, добавляет новый атрибут [[trivially_relocatable]]
, которым можно пометить типы, которые можно передавать более эффективно, чем через move. А именно, мы копируем объект на новое место через memcpy
и забываем про первоначальный объект, не вызывая для него деструктор. Правда, таким образом нельзя перемещать локальные переменные, так как компилятор вызывает их деструкторы за нас, не спрашивая, и у этой проблемы нет простого решения.
Атрибут можно применить к вашим классам при их определении. На практике атрибут будет нужен нечасто: компилятор автоматически помечает класс [[trivially_relocatable]]
, если все его члены являются таковыми, и вы не определили кастомные move-конструктор с деструктором (rule of zero). Классы стандартной библиотеки будут помечены [[trivially_relocatable]]
для повышения производительности существующего кода, однако какие именно будут помечены, оставляется на усмотрение реализации. std::vector
и прочие будут использовать новую функцию relocate_at
, которая делает relocation или move, в зависимости от того, что тип поддерживает.
template <typename T>
class [[trivially_relocatable]] unique_ptr { ... };
std::vector<unique_ptr<widget>> v;
for (auto x : ...) {
// Старые unique_ptr перемещаются через relocation, а не move
v.push_back(std::make_unique<widget>(x));
}
С proposal есть несколько проблем, которые обсуждаются:
[[trivially_relocatable]]
, даже если его члены таковыми не являются. Например, таким образом можно сломать std::mutex
, обернув его в свой [[trivially_relocatable]]
классstd::unique_ptr<T>
по-прежнему будет передаваться в функции как указатель на указательРассмотренный выше proposal применим тогда, когда объект приходится перемещать, но можно сделать это эффективнее, чем сейчас. Тем не менее, в том случае указатели на объект всё равно «ломаются». В отличие от него, P2025 [5] позволяет устранить саму причину перемещений в некоторых случаях.
C++17 исключил перемещения, когда мы вычисляем значение в return
и тут же возвращаем его. Это называется Return Value Optimization (RVO). P2025 исключает также перемещения, когда мы возвращаем локальную переменную (NRVO). При этом она может быть не-перемещаемой, вроде std::mutex
или наших абстрактных типов данных:
widget setup_widget(int x) {
return widget(x); // OK, C++17
}
widget setup_widget(int x) {
auto w = widget(x);
w.set_y(process(x));
return w; // OK, P2025
}
Кстати, proposal мой :) В течение пары дней перенаправлю ссылку на wg21.link
.
Фактически, предлагается [6] аналог @autoclosure
из Swift. Параметр функции может быть помечен специальным образом, чтобы соответствующий аргумент при вызове автоматически оборачивался в лямбду. Перемещение при таком способе передачи параметров не происходит, объект создаётся сразу там, где нужно:
void vector<T>::super_emplace_back([] -> T value) {
void* p = reserve_memory();
new (p) T(value());
}
vector<widget> v;
v.super_emplace_back(widget()); // нет move
v.super_emplace_back([&] { return widget(); }); // под капотом
Это решение [7] более общее, чем предыдущее, и затрагивает также другие проблемные темы. Сокращённый синтаксис лямбда-выражений сделает работу с коллекциями и «ленивыми параметрами» в C++ такой же приятной, как и в нормальных других языках. Правда, с синтаксисом P0573 есть проблемы, но я готов предложить несколько других вариантов, к тому же, более коротких:
// Текущий синтаксис
auto add = [&](auto&& x, auto&& y) { return x + y; };
auto dbl = [&](auto&& x) { return x * 2; };
auto life = [&] { return 42; };
// P0573
auto add = [&](x, y) => x + y;
auto dbl = [&](x) => x * 2;
auto life = [&]() => 42;
// Мой #1: из Rust
auto add = |x, y| x + y;
auto dbl = |x| x * 2;
auto life = || 42;
// Мой #2
auto add = x y: x + y;
auto dbl = x: x * 2;
auto life = :42;
На этом всё! Желаю всем предложениям исправить пробелы и быть принятыми в C++23. Любые вопросы, замечания, пожелания оставляйте в комментариях.
Автор: Anton3
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/343605
Ссылки в тексте:
[1] словами: https://youtu.be/SGdfPextuAU?t=1465
[2] раз: https://www.youtube.com/watch?v=xxta6LEn9Hk
[3] два: https://youtu.be/SGdfPextuAU?t=1200
[4] Это предложение: https://wg21.link/P1144R4
[5] P2025: https://rawcdn.githack.com/Anton3/cpp-proposals/db611c48ca00752969ea03f2d39ef77e5a11e132/draft/d2025r0.html
[6] предлагается: https://wg21.link/P0927R2
[7] Это решение: https://wg21.link/P0573
[8] Источник: https://habr.com/ru/post/484380/?utm_campaign=484380&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.