【问题标题】:C++: is return value a L-value?C ++:返回值是L值吗?
【发布时间】:2011-08-31 23:58:43
【问题描述】:

考虑这段代码:

struct foo
{
  int a;
};

foo q() { foo f; f.a =4; return f;}

int main()
{
  foo i;
  i.a = 5;
  q() = i;
}

没有编译器抱怨它,即使是 Clang。为什么q() = ... 行是正确的?

【问题讨论】:

  • 哇,好问题,+1。
  • 你认为这段代码有什么问题? q() 返回一个结构,然后为它分配一个值。这有什么问题?
  • @Andy:我认为这很容易出错,因为将值分配给返回值通常不会做任何事情(除非 operator= 正在做一些魔术,这可能是不好的设计实践)。我原以为这是一个警告,就像不使用局部变量一样。
  • @Andy Johnson:普遍存在的误解是人们认为您可以在赋值左侧使用的所有内容都必须是左值。在这种情况下,似乎违反了这一要求。但实际上并没有这样的要求。内置赋值确实需要它的 LHS 上的左值,但重载赋值不需要。在这种情况下,我们正在处理重载的,即使它并不明显。

标签: c++ return-value lvalue


【解决方案1】:

除了其他好的答案之外,我想指出std::tie 在这种机制之上工作,用于从另一个函数解包数据。 See here。所以它本身不容易出错,请记住它可能是一种有用的设计模式

【讨论】:

    【解决方案2】:

    一个有趣的应用:

    void f(const std::string& x);
    std::string g() { return "<tag>"; }
    
    ...
    
    f(g() += "</tag>");
    

    这里,g() += 修改临时,这可能比使用 + 创建额外的临时更快,因为为 g() 的返回值分配的堆可能已经有足够的空闲容量来容纳 &lt;/tag&gt;

    看到它运行at ideone.com with GCC / C++11

    现在,哪位计算新手谈到了优化和邪恶……? ;-]。

    【讨论】:

      【解决方案3】:

      不,函数的返回值是左值当且仅当它是引用 (C++03)。 (5.2.2 [expr.call] / 10)

      如果返回的类型是基本类型,那么这将是一个编译错误。 (5.17 [expr.ass] / 1)

      这样做的原因是您可以在类类型的 r 值上调用成员函数(甚至是非const 成员函数),并且 foo 的分配是实现定义的成员函数:@987654323 @。第 5 节中对运算符的限制仅适用于内置运算符,(5 [expr] / 3),如果重载决议为运算符选择重载函数调用,则该函数调用的限制适用而是。

      这就是为什么有时建议将类类型的对象返回为const 对象(例如const foo q();)的原因,但是这可能会在 C++0x 中产生负面影响,因为它可能会阻止移动语义在它们的作用下工作应该。

      【讨论】:

      • 举个具体的例子,考虑std::cout &lt;&lt; "Hello, world" &lt;&lt; std::endl。第一个operator&lt;&lt; () 返回一个左值,您可以在其上调用第二个operator&lt;&lt; ()。因此,这种对返回值调用方法的做法比许多人想象的要普遍。
      • 这让我想起了The Design and Evolution of C++ 的一句话:这就是多年来逐渐成为 C++ 设计经验法则的概念的起源:用户定义和构建-in 类型应该相对于语言规则表现相同,并从语言及其相关工具中获得相同程度的支持。在制定理想时,内置类型获得了迄今为止最好的支持,但 C++ 已经超越了该目标,因此与用户定义的类型相比,内置类型现在获得的支持略逊一筹。
      • @MSalters 是的,但在cout 的情况下,它实际上确实返回一个左值(它返回自身并通过引用返回*this),而不是按值返回.所以有点不同。
      • @MSalters: ostream::operator
      • @MSalters 在std::cout &lt;&lt; "duh" &lt;&lt; std::endl 中,operator&lt;&lt; 都返回非常量引用(它们是左值),并且因为运算符都需要非常量引用作为它们的第一个参数,所以原来的 @987654334 @object 不能是右值。在其他示例中(我认为这是问题的动机),非常量函数被临时调用。
      【解决方案4】:

      因为可以分配结构,并且您的q() 返回struct foo 的副本,因此它将返回的结构分配给提供的值。

      在这种情况下,这实际上并没有做任何事情,因为结构后来超出了范围,并且您首先没有保留对它的引用,所以无论如何您都无法对它做任何事情(在这个特定的代码)。

      这更有意义(尽管仍然不是真正的“最佳实践”)

      struct foo
      {
        int a;
      };
      
      foo* q() { foo *f = new malloc(sizeof(foo)); f->a = 4; return f; }
      
      int main()
      {
        foo i;
        i.a = 5;
      
        //sets the contents of the newly created foo
        //to the contents of your i variable
        (*(q())) = i;
      }
      

      【讨论】:

      • that 和 this 有什么区别:int blah() { int f = 4; return f; } int main() { int a = 99; blah() = a; } 结构体有什么神奇之处吗?因为该代码无法编译。
      • @Seth 查看 charles Bailey 的回答
      • Int 没有成员函数,所以它们也没有int::operator=(int)。这就是结构的“神奇”之处:它们有默认方法。
      • @Seth,因为在您的示例中,该函数返回一个 int,而不是一个对象,因此您不能为它分配任何内容。如果函数返回对 int ( int& ) 的引用,您可以分配给它。
      • @bruno: s/nothing/anything/
      猜你喜欢
      • 1970-01-01
      • 2014-09-20
      • 2012-11-03
      • 2011-01-11
      • 1970-01-01
      • 2014-11-25
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      相关资源
      最近更新 更多