cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Функция принудительного завершения времени жизни локальной переменной #546

Open Centimo opened 1 year ago

Centimo commented 1 year ago

Кратко Предлагается добавить в стандарт функцию, которая бы позволяла принудительно завершать время жизни локальной переменной, делая её недоступной для дальнейшего использования. Также предлагается реализовать copy elision (или его аналог) для xvalues вида const T&&.

Проблема На данный момент в C++ нет возможности убрать из скоупа переменную. В лучше случае, мы можем сделать std::move, который оставит после себя обнулённый (в зависимости от определения) объект. Пример:

{
  std::shared_ptr< Some > local_variable = std::make_shared< Some >(...);

  ...

  some_function(std::move(local_variable));

  ...

  local_variable->some_method(); // runtime (!) error
}

Предложение Добавить функцию - пусть это будет, например, std::drop, - которая бы возвращала по аналогии с std::move xvalue expression, но при этом бы гарантировала ошибку компиляции в случае повтороного доступа к переменной. Тогда пример выше можно было бы написать следующим образом:

{
  /* [const] */ std::shared_ptr< Some > local_variable = std::make_shared< Some >(...);

  ...

  some_function(std::drop(local_variable)); // тут переменная "перемещается в функцию" `some_function`, но удаляется из локального скоупа

  ...

  // local_variable->some_method(); // _compile_ time (!) error: "local_variable was dropped before at line ..."
  // auto local_variable{}; // _compile_ time (!) error - переопределение должно быть запрещено
  // decltype(local_variable) new_variable{}; // _compile_ time (!) error - такое тоже, вероятно, стоит запретить
}

Если сделать переменную local_variable константной, то std::move теряет свой смысл (константная переменная должна "обнулиться", что явно не самый ожидаемый результат), а вот std::drop остаётся актуальной - нет смысла заботиться о константности переменной, удалённой из скоупа. То есть функция std::drop должна либо преобразовывать const T&& в T&&, либо реализовать copy elision. Последний вариант кажется более удачным. Ещё один пример для дискуссии:

{
  std::shared_ptr< Some > local_variable = std::make_shared< Some >(...);

  ...

  // использование std::drop внутри условных операторов тоже стоит запретить. Как и внутри `switch .. case` и `try ... catch`.
  /*
  if (is_drop) {
    std::drop(local_variable);
  }
  */ 

  // Использование функции вместе с её аргументом в тех случаях, когда порядок вычисления не определён, не допускается
  // some_function(std::drop(local_variable), local_variable);
  ...

  // а вот такое, возможно, стоит разрешить
  if constexpr (constexpr_is_drop) {
    std::drop(local_variable);
  }
}

Ещё пример. Рассмотрим следующую структуру:

struct Message {
  const std::string _text;

  Message() = delete;
  Message(const Message&) = delete;
  Message(Message&&) = delete;
  Message(const Message&&);
  Message operator = (Message&&) = delete;
  Message operator = (const Message&&);

  static std::unique_ptr< Message > make();
};

Тут всё просто: сообщение, которое: а) Константно. Если мы создали сообщение (например, с помощью статического метода класса Message), то оно не должно меняться. б) Существует в единственном экземпляре, для чего мы запретили конструктор копирования. в) Не может быть пустым, для чего мы запретили конструктор и оператор перемещения и конструктор по умолчанию.

Пусть мы хотим после этого определить следующий тип:

struct Package {
  const Message _message;
  const int _id;

  Package(const Message&& message, int id) 
    : _message(std::forward< const Message >(message) // копирование, а не перемещение
    // : _message(message) // ошибка компиляции (конструктор копирования `Message` удалён)
    // : _message(std::forward< Message >(message) // ошибка компиляции (_message константна)
    , _id(id)
  {}
};

int main() {
  const Message message = ...;

  ...

  const Package package(std::move(message), 3); // копирование
  // const Package package(std::drop(message), 3); // перемещение
}

Ошибки компиляции логичны, иначе нарушались бы гарантии константности. Но с функцией std::drop можно было бы реализовать перемещение констант.

Предложение суммарно: 1) Добавить функцию, позволяющую удалять локальные переменные из скоупа. 2) Реализовать copy elision (или его аналог) для xvalues вида const T&& (с учётом случаев первого пункта).

Плюсы: 1) Появляется возможность сделать код более понятным и безопасным 2) Расширяются возможности при работе с константами 3) Добавляются подсказки для компиляторов/статических анализаторов

Минусы: 1) Возможно, чтобы разделить временные объекты и перемещаемые константы, придётся добавлять новый тип выражений (?) ? ...

Обратная совместимость. Реализация функции std::drop не нарушает обратную совместимость. Расширение copy elision для xvalues вида const T&& может нарушить обратную совместимость, если не будет привязано к использованию функции std::drop.