Почему перемещение std :: optional не сбрасывается

Я был довольно удивлен, узнав, что конструктор перемещения (и назначение в этом отношении) std::optional не сбрасывает необязательный перемещенный из него, как видно из [19.6.3.1/7], в котором говорится: "bool (rhs) без изменений ".

Это также можно увидеть по следующему коду:

#include <ios>
#include <iostream>
#include <optional>
#include <utility>

int main() {
  std::optional<int> foo{ 0 };
  std::optional<int> bar{ std::move(foo) };

  std::cout << std::boolalpha
            << foo.has_value() << '\n'  // true
            << bar.has_value() << '\n'; // true
}

Это, по-видимому, противоречит другим примерам перемещения в стандартной библиотеке, например, с std::vector котором перемещенный контейнер обычно сбрасывается каким-либо образом (в случае вектора, который, как гарантируется, будет пустым впоследствии), чтобы "аннулировать" его, даже если объекты, содержащиеся в нем сами, уже перемещены. Есть ли какая-либо причина для этого или потенциального прецедента, которое должно принять такое решение, например, возможно, пытающееся имитировать поведение не факультативной версии того же типа?

Ответы

Ответ 1

Если не указано иное, перемещенный объект типа класса остается в действительном, но неуказанном состоянии. Не обязательно "состояние перезагрузки" и определенно не "недействительно".

Для примитивных типов перемещение аналогично копированию, т.е. Источник не изменяется.

По умолчанию конструктор move-type для типа класса с примитивными элементами будет перемещать каждый элемент, т.е. Оставить примитивные элементы неизменными; пользовательский конструктор перемещения может или не может "перезагрузить" их.

Перемещенный вектор может содержать или не содержать в нем элементы. Мы ожидаем, что это не так, поскольку это эффективно, но на него нельзя положиться.

std::string может содержать элементы в ней из-за Small String Optimization.


move on std::optional на самом деле определяется стандартом (С++ 17 [optional.ctor]/7). Он определяется как move по содержащемуся типу, если оно присутствует. Он не превращает ценный факультативный в необязательный вариант.

Таким образом, на самом деле ожидается, что ваш код выводит true true, а фактическое содержащееся значение в foo должно оставаться неизменным.


Что касается вопроса о том, почему std::optional move-constructor определяется следующим образом: я не могу сказать точно; но optional не похож на вектор с максимальным размером 1. Он больше похож на переменную с флагом правильности, на который наложен флажок. Соответственно, имеет смысл перемещать optional как перемещение переменной.

Если вы перемещаете optional старая старая "пустая", то a = std::move(b); будет ссылаться на деструктор b управляемого объекта, что было бы неожиданным (по крайней мере, для меня).

Ответ 2

Одним словом: производительность.

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

Чтобы помочь этой цели, стандартная практика перемещается из объектов в действительное, но неуказанное состояние. Таким образом, самый минимальный optional который необходимо выполнить для построения/назначения перемещения, - это перейти от аргумента источника. Чтобы указать, что источник не имеет значения после того, как перемещение эквивалентно произношению:

После того, как вы переедете, сделайте дополнительную, ненужную работу.

Независимо от того, насколько мала эта дополнительная работа, она отлична от нуля. Некоторые (и я смею сказать, что многие) клиенты не будут нуждаться в дополнительной работе и не должны платить за нее. Клиенты, которые действительно нуждаются в этом, могут легко добавить x.reset() после переезда, поставив x.reset() из optional в заданное состояние.

Ответ 3

В этом параграфе говорится, что если это необязательное значение имеет значение, оно все равно имеет значение. Поскольку это значение было перенесено из (к вновь созданному объекту), оно может отличаться от того, что было до перемещения. Это позволяет вам получить доступ к необязательному объекту с перетаскиванием так же, как перемещенный из необязательного объекта, поэтому поведение T vs. optional<T> (когда оно содержит объект) при доступе после перемещения является так же.

Кроме того, общий эффект перехода от опционального зависит от того, как содержащийся тип T обрабатывает перемещение. Другие классы (например, vector) не имеют этой зависимости.

Ответ 4

Хотя было бы разумно ожидать, что std::optional ведет себя подобно std::unique_ptr который сбрасывает состояние перемещенного объекта, существуют причины не требовать такого поведения. Я думаю, что одним из них является то, что std::optional option тривиального типа должен быть тривиально копируемым типом. Как таковой, он не может иметь не has_value умолчанию конструктор перемещения и не может сбросить свой флаг has_value.

Наличие std::optional для нетривиального типа ведет себя иначе, чем std::optional для тривиального типа, это довольно плохая идея.