std :: option: эффективная разница между простым и ref-квалифицированным значением () - PullRequest
1 голос
/ 19 июня 2019

Это академический вопрос. Тип std::optional<T> имеет метод T && value() &&. У меня есть следующие определения:

class A { ... };
void f(A &&a);

И следующая программа:

std::optional<A> optA;
optA = A(1337);
f(std::move(optA).value()); // OPTION 1
f(std::move(optA.value())); // OPTION 2
std::cout << optA.has_value() << std::endl;

Есть ли существенная разница между ВАРИАНТОМ 1 и ВАРИАНТОМ 2? Для ОПЦИИ 1 у меня будет 1, 0 или не указано в качестве вывода? Согласно моим тестам has_value () осталась верной в обоих случаях.

Есть ли возможная ситуация, когда value() && имеет значение для std::move и value()?

Ответы [ 4 ]

4 голосов
/ 19 июня 2019

Нет разницы между std::move(optA).value() и std::move(optA.value()). value() просто возвращает glvalue со ссылкой на содержащееся в нем значение, и вы можете value() вернуть lvalue, которое затем преобразуется в xvalue на std::move, или вы можете сначала вызвать std::move и получить value() вы xvalue сразу (в действительности, преобразование из lvalue в xvalue будет происходить где-то внутри самого метода value()). Переопределенные с помощью ref перегрузки, очевидно, не очень полезны в этом простом случае, но могут быть полезны, когда необязательный параметр был передан путем переадресации ссылки O&& o, и вы хотите, чтобы std::forward<O>(o).value() «сделал все правильно».

3 голосов
/ 19 июня 2019
f(std::move(optA).value()); // OPTION 1

Вы "переехали" optA, но это не значит, что вы изменили это. Обычно вы не должны использовать значение после того, как оно было «перемещено», поскольку оно находится в допустимом, но неопределенном состоянии. std::move - это просто приведение типа (точно так же, как static_cast<A&&>(optA)). Движущийся конструктор не был вызван, потому что новый экземпляр std::optional<A> не был создан. Следовательно, has_value возвращает true.

В этом случае T && value() && называется действительно.

f(std::move(optA.value())); // OPTION 2

T && value() && здесь не вызывается, потому что optA не является &&. Таким образом, вы получаете A& и приводите его к A&& на std::move, а затем переходите к f, который предположительно ничего не делает. optA не был изменен и все еще сообщает, что он содержит значение.

1 голос
/ 19 июня 2019

Есть ли возможная ситуация, когда value () && имеет значение по сравнению с std :: move и value ()?

Рассмотрим следующее:

optional<A> func() {...}

void f(optional<A> opt) {...}
void g(A a) {...}

f(func());
g(func().value());
Параметр

f opt будет инициализирован при перемещении. Технически, он будет инициализирован непосредственно значением prvalue, но предварительный C ++ 17 означает, что он инициализируется при перемещении. Эта инициализация может быть исключена, но если это не так, то это делается с помощью перемещения. Всегда.

А как насчет параметра g? Что должно произойти? Хорошо, рассмотрим, что это будет делать:

struct C {string s;};
C func2() {...}

void h(string s);

h(func2().s);

Параметр h инициализируется с помощью move . Зачем? Потому что, если вы обращаетесь к подобъекту члена prvalue, полученное выражение будет xvalue и, следовательно, может перемещаться без явного использования std::move.

Конструктор && optional гарантирует, что value работает так же. Если вы вызываете его для временного значения prvalue, он возвращает значение xvalue, которое можно перемещать без явного вызова std::move. Таким образом, в исходном случае параметр g инициализируется перемещением, точно так же, как если бы он обращался к подобъекту-члену типа prvalue.

0 голосов
/ 19 июня 2019

Я бы предположил, что обе версии одинаковы, однако ответ Никола Боласа выглядит интересным.

Если предположить, что оба результата приводят к значению r, а код не имеет значения, у нас нет причиныигнорировать читабельность.

  std::move(optA).value()

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

Некоторые линтеры также распознают это как переменную, к которой больше не нужно прикасаться, что предотвращает использование после перемещения.

std::move(optA.value())

Это, однако, преобразуетвернуть значение в rvalue.Я в основном считаю этот код ошибкой.Либо функция возвращает значение (или постоянную ссылку), и мы написали много кода.В противном случае функция возвращает lvalue, и вы потенциально разрушаете внутреннее состояние переменной.

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

...