Некоторые различия между xvalue и prvalue
В последнее время я внимательно изучаю категории C++. Разница между lvalue и rvalue кажется очевидной, но я запутался, когда дело доходит до prvalue и xvalue.
Учитывая приведенный ниже пример:
#include <iostream>
using std::cout;
using std::endl;
using std::move;
class Type {
public:
int value;
Type(const int &value=0) :value(value) {}
Type(const Type &type) :value(type.value){}
Type(Type &&type) noexcept :value(type.value) {}
Type &operator= (const Type &type) {
value = type.value;
return *this;
}
Type &operator=(Type &&type) noexcept{
value = type.value;
return *this;
}
};
Type foo1(const Type &value) {
return Type(value);
}
Type &&foo2(const Type &value) {
return Type(value);
}
int main() {
Type bar1(123);
cout << foo1(bar1).value << endl;
cout << foo2(bar1).value << endl;
Type bar2;
bar2 = foo1(bar1);
cout << bar2.value << endl;
bar2 = foo2(bar1);
cout << bar2.value << endl;
return 0;
}
Запустив пример, консоль выдаст:
123
123
123
-858993460
Кто-нибудь может объяснить, почему в последнем выводе выдается неожиданное значение?
Какую особенность xvalue показывает этот пример?
2 ответа
foo2
возвращает ссылку, привязанную к временному объекту, который немедленно уничтожается; он всегда возвращает висящую ссылку.
временная привязка к возвращаемому значению функции в операторе возврата не расширяется: она немедленно уничтожается в конце выражения возврата. Такая функция всегда возвращает висящую ссылку.
Разыменование возвращенной ссылки, например
foo2(bar1).value
и
bar2 = foo2(bar1);
ведет к UB; все возможно.
С другой стороны,
foo1
нет такой проблемы. Возвращаемое значение перемещается из временного объекта.
Вот очень простое объяснение. Рассмотрим этот пример из учебника времен до C++0x:
T& f()
{
T t;
return t;
}
// ...
T& val = f();
cout << val; // <--- SIGSEGV here
Вы же не удивлены, что этот фрагмент рухнул, верно? Вы вернули ссылку (которая под капотом является не чем иным, как прославленным указателем) на локальный объект, который был уничтожен еще до того, как функция возвратилась, тем самым обрекаяval
стать висячей ссылкой.
Теперь рассмотрим:
T&& f()
{
T t;
return static_cast<T&&>(t);
}
// ...
T&& val = f();
cout << val; // <--- SIGSEGV here
С точки зрения времени жизни объекта это точно так же, как и раньше. Ссылка Rvalue — это не какой-то совершенно новый инструмент для волшебного перемещения объектов. Это та же самая старая ссылка (читай: прославленный указатель). Ничего не изменилось - вы по-прежнему возвращали адрес уничтоженного объекта.
И я предполагаю, что все согласны с тем, что это не более чем прославленныйstatic_cast<T&&>
, поэтому в случае его использования результат тот же:
T&& f()
{
T t;
return std::move(t);
}
// ...
T&& val = f();
cout << val; // <--- SIGSEGV here
Этот пример на 99,9% идентичен предыдущему.
(Разница в 0,1% состоит, например, в том, что в первых двух примерах GCC знает , что код облажался, и фактически возвращает нулевую ссылку, которая гарантированно рухнет при первом использовании; в то время как в последнем примере, поскольку
std::move
является функцией, она не может быть уверена, поэтому послушно возвращает неверный адрес).