ideas icon indicating copy to clipboard operation
ideas copied to clipboard

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

Open Centimo opened this issue 2 years ago • 0 comments

Кратко Предлагается добавить в стандарт функцию, которая бы позволяла принудительно завершать время жизни локальной переменной, делая её недоступной для дальнейшего использования. Также предлагается реализовать 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.

Centimo avatar Jan 28 '23 17:01 Centimo