Присвоение Rvalue, возвращенного из функции другому Rvalue
class Test {
public:
int n1;
};
Test func() {
return Test();
}
int main() {
func() = Test();
}
Это не имеет смысла для меня. Как и почему это разрешено? Это неопределенное поведение? Если функция возвращает значение rvalue, то как можно установить значение rvalue для другого значения rvalue? Если бы я попробовал это с любыми примитивными типами, это дало бы мне ошибку, как я и ожидал.
Я знаю, что lvalue - это место в памяти, поэтому функция создает временное lvalue (rvalue?) И присваивает его другому lvalue? Может кто-нибудь объяснить, как работает этот синтаксис?
2 ответа
Категория значений выражения вызова функции на самом деле является rvalue.
Действительно, вы не можете вызывать оператор присваивания копии для примитивов rvalue. Историческое определение значений в C было фактически различием, что они не могут быть на левой стороне определения.
Однако операторы присваивания классов немного отличаются. Это обычные функции-члены. И никакое правило не запрещает вызывать функции-члены из rvalues. Действительно, часто бывает полезно, когда функции имеют побочные эффекты.
Как это работает, ну, оператор копирования назначается на временный, оператор копирует аргумент правой руки, изменяя состояние временного. После утверждения временный объект отбрасывается. Там нет UB, просто бессмысленное копирование.
Вы можете запретить вызов копирования в rvalue, объявив оператор со ссылочным квалификатором, например так: Test& operator=(Test) & = default;
, Ref-qualifiers были добавлены только позже в C++11, поэтому (неявное) присвоение копии не могло быть указано, чтобы быть ref-квалифицированным ранее. Предположительно, C++11 не изменил квалификатор неявного конструктора копирования, чтобы предотвратить разрушение старого кода, который присваивает значение r, даже если такое присваивание кажется довольно бессмысленным. Стандарт кодирования High Integrity C++ рекомендует использовать квалификатор ref с определенными пользователем операторами копирования.
Существует довольно крутая кривая обучения в том, что касается категорий ценностей (по крайней мере, для меня), но я полагаю, что вы правильно поняли терминологию на своем примере. Так
func()
действительно возвращает значение и из C++ Standard par. 3.10.5 (у меня только текущий черновик, номер вашего абзаца может отличаться) мы читаем:
Lvalue для объекта необходимо для того, чтобы изменить объект, за исключением того, что rvalue типа class может также использоваться для изменения его референта при определенных обстоятельствах. [Пример: функция-член, вызываемая для объекта (9.3), может модифицировать объект. - конец примера]
Таким образом, оператор присваивания, который является функцией-членом, как в примере, упомянутом в стандарте, является исключением из правила, позволяя изменять значения.
Это вызвало много критики в мире программирования, и самым ярким примером этого является отрывок из C++ FQA:
(Да, смертельные ловушки, покрытые сахаром, вы, бестолковые болельщики.
X& obj=a.b().c()
- упс,b()
является временным объектом и c() возвращает ссылку на него! Не должен был назначить это на ссылку. Не так много шансов для предупреждения компилятора.)
Но в реальном программировании на C++ есть промышленные приложения, такие как именованный параметр idiom:
std::cout << X::create().setA(10).setB('Z') << std::endl;