为什么可以将值类类型的 rvalue 赋值给它?

13
为什么这段代码能够编译通过?我原以为构造函数返回的右值不会被存储在内存中,因此不能作为左值使用。
class Y {
public :
    explicit Y(size_t num = 0) {}
};

int main() {
    Y(1) = Y(0); // WHAT?!?
    return 0;
}

4
成员函数 operator= 可以在任何对象上调用。你的代码与 Y(1).operator=(Y(0)); 相同。 - M.M
2个回答

14
合成赋值运算符根据见12.8 [class.copy]第18段声明为以下之一(如果可以合成且未声明为已删除):
  • Y& Y::operator=(Y const&)
  • Y& Y::operator=(Y&) ()
也就是说,对于任何没有特定声明 ref-qualifiers 的其他成员函数一样,它适用于右值。
如果要防止赋值操作左侧出现临时对象,则需要相应地声明。
class Y {
public :
    explicit Y(std::size_t num = 0);
    Y& operator= (Y const&) & = default;
};

标准使用ref-qualifier这个名称来表示= default之前的&。相关提案是N2439。我不知道有没有关于ref-qualifiers的好描述。在this question中有一些信息。

1
我尝试了这个并且它有效。在'= default'之前的'&'叫什么,我在哪里可以阅读更多关于这个不熟悉的语法? - John Difool
1
@JohnDifool 这里有另一个关于此主题的线程。如果你在 stackoverflow 上搜索 "ref qualifier",你应该能找到更多相关信息。 - M.M
1
@JohnDifool:我的回答提到了“引用限定符”,但我同意这个非正式的名称并不是很有帮助,因此我已经相应地更改了答案。 - Dietmar Kühl
我在C++ Primer书中找到了这个内容:“引用限定符:用于指示非静态成员函数可以在lvalue或rvalue上调用的符号。限定符&或&&跟随参数列表或const限定符(如果有)。由&限定的函数只能在lvalue上调用;由&&限定的函数只能在rvalue上调用。” 我可能需要进行一些挖掘才能理解最后一部分的含义。 - John Difool
在更多的阅读和思考之后,我仍然有一个问题想问:您建议添加“Y& operator= (Y const&) & = default;”是有效的,但这不等同于说您需要默认的“合成”行为吗?换句话说,为什么它可以使用“default”,而我认为应该使用“delete”呢? - John Difool
@JohnDifool:运算符的声明和其行为之间存在差异。它的合成声明试图尽可能通用,因此允许对rvalue进行赋值。所要求的合成定义使用= default实现了每个子对象的赋值操作。如果您= delete了该操作,则所有赋值都将被禁止,因为任何赋值运算符的声明都会抑制其他任何赋值。要禁止对rvalue进行赋值,您还需要= delete &&版本而不是&版本。 - Dietmar Kühl

1

我不确定你从哪里得到了那个具体的经验法则。如果有,一个经验法则是(来自Scott Meyers):如果它有一个名称,那么它是一个lvalue。

在这种情况下,您正在创建一个临时对象并将其传递给赋值方法/函数。这没有问题。事实上,这样做甚至可能是有意义的,例如

// Applies foo to a copy, not the original.
(Y(1) = y).foo()

虽然在这里Y(*)没有名称,因此它们是右值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接