【问题标题】:What is the logic behind the "lvalueness" and "rvalueness" of a value returned by a function函数返回值的“左值”和“右值”背后的逻辑是什么
【发布时间】:2019-02-27 14:50:08
【问题描述】:

在下面的代码中,我期待函数 Foo f(Foo& x) 返回一个右值,但令我惊讶的是它没有。所以我开始测试一些案例,以找出返回值的“左值”和“右值”背后的逻辑。事实证明,返回一个类类型的命名变量是一个左值。也许我错过了什么

#include <iostream>

class Foo
{
    int val;
public:
    Foo(int value=10)
    {
        val = value;
    };

    Foo(Foo& other)
    {
        std::cout << "copy ctor used" << std::endl;
        val = other.val;
    };
    Foo(Foo&&)
    {
        std::cout << "move ctor used" << std::endl;
    };
    Foo operator=(Foo& other) 
    {

        std::cout << "copy assign used" << std::endl;
        val = other.val;
        return *this;
    }
    Foo operator=(Foo&& a)
    {
        std::cout << "move assign used" << std::endl;
        return *this;
    }
    void set(int value) { val = value; };
    int get() { return val; };

};


int z = 20;;
int case1()
{
    return z;
};

int case2(int x)
{
    return x;
};

Foo case3(Foo x)
{
    return x;
};

Foo case4(Foo& x)
{
    return x;
};

Foo y;
Foo case5()
{
    return y;
}


Foo& case6(Foo& x)
{
    return x;
};

Foo case7()
{
    Foo x;
    return x;
}



int main()
{
    
    /*case1() = 50;*/ // as expected: not OK the return value is not an lvalue

    int c = 40;
    /*case2(c) = 50; */ // as expected: not OK the return value is not an lvalue
    
    Foo a;
    Foo b = 30;
    case3(a) = b; // OK: not at all expected the return value is an lvalue
                  // I don't see the difference with case 2
                  //copy construction of the argument and return value
                  //copy assigning of b

    case4(a) = b; //OK: behaving exactly like case 3 except for the copy construction
                  // of the argument and the return value
                  // copy assigning taking place as expected

    case5() = b; // same as case 3

    case6(a) = b; //just copy assigning taking place
                  // the same reference to a is returned    

    case7() = b;  // same as before
    std::cin.get();
}

【问题讨论】:

  • 你的结论是错误的。你假设= 只能应用于可能运算符重载的世界中的左值是错误的。
  • 在函数按值返回Foo 并分配给它的情况下,您只是在调用临时对象的operator=。您可以通过提供operator= 的不同引用限定符版本来进一步区分这些情况,请参阅here
  • 另一个类似主题的问题(虽然可能不是重复的)stackoverflow.com/questions/51172341/…
  • 抱歉,您真的确定要以您的经验水平深入研究价值(现代 C++ 复杂得难以置信)吗?
  • @SergeyA 我们总有一天要处理这样的事情,所以越快越好:)

标签: c++


【解决方案1】:

在除case6() 之外的所有案例函数中,您按值返回,因此您确实有一个右值表达式。

case1()case2() 不起作用但其他的原因是因为内置类型的赋值运算符仅适用于左值。用户定义的类型没有这样的限制,除非您自己通过在赋值运算符中添加引用限定符来添加它。

将复制赋值运算符更改为

Foo operator=(Foo& other) & //<- notice the & at the end
{
    std::cout << "copy assign used" << std::endl;
    val = other.val;
    return *this;
}

将导致除case6() 之外的所有情况无法编译,因为尾随&amp; 表示您只能在左值上调用复制赋值运算符。

【讨论】:

  • 我完全相信你的回答。但是,如果我添加声明 Foo c(case4(a)) 期望调用移动构造函数,我会调用复制构造函数,就像函数返回左值一样!
  • @SoulimaneMammar 你看到的是复制省略。在Foo c(case4(a)) 中,由于函数通过引用获取,它必须复制a 以填充返回值。然后它会将该副本省略到c 占用的存储中。如果没有复制省略,您将看到一个副本并移动:coliru.stacked-crooked.com/a/fbdee0c96bad1dd8
【解决方案2】:

您无法通过检查调用了哪些构造函数来确定返回值的值类别,因为编译器在某些情况下可以省略复制和移动,消除用于保存返回值的局部变量 (NRVO),并转换复制到动作中。

但是,值类别规则很简单:

  • 如果函数声明的返回类型是左值引用,则返回值是左值。
  • 如果函数声明的返回类型是右值引用,则返回值是 xvalue。
  • 否则,返回值为纯右值。

(函数有一个例外:函数值总是左值。)

【讨论】:

  • 在我给出的示例的所有情况下(除了情况 6),返回值既不是左值引用也不是右值引用,因此返回值类别是 pvalue!还是我错了?
猜你喜欢
  • 1970-01-01
  • 2013-03-23
  • 1970-01-01
  • 2011-01-03
  • 2020-01-03
  • 1970-01-01
  • 1970-01-01
  • 2011-07-22
  • 1970-01-01
相关资源
最近更新 更多