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