C ++:返回值是L值吗?


73

考虑以下代码:

struct foo
{
  int a;
};

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

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

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


6
哇,好问题,+ 1。
塞斯·卡内基

5
您认为此代码有什么问题?q()返回一个结构,然后为它分配一个值。怎么了
安迪·约翰逊

1
@Andy:我认为这很容易出错,因为将值分配给返回值通常不会执行任何操作(除非operator =做一些魔术,这可能是不良的设计实践)。我期望这是一个警告,就像不使用局部变量一样。
约翰,

1
@安迪·约翰逊(Andy Johnson):普遍的误解是人们认为作业左侧的所有内容都必须是左值。在这种情况下,似乎违反了此要求。但是实际上并没有这样的要求。内置分配确实需要在其LHS上加上lvalue,而重载则不需要。在这种情况下,即使不是很明显,我们也要处理过载的情况。
AnT 2011年

Answers:


69

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

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

之所以可行,是因为允许您在const类类型的r值上调用成员函数(甚至是非成员函数),并且的分配foo是实现定义的成员函数:foo& foo::operator=(const foo&)。第5节中对运算符的限制仅适用于内置运算符(5 [expr] / 3),如果重载解析为某个运算符选择了一个重载函数调用,则该函数调用的限制将适用。

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


18
举一个具体的例子std::cout << "Hello, world" << std::endl。第一个operator<< ()返回一个左值,您可以在其上调用第二个operator<< ()。因此,这种对返回值进行调用的方法比许多人想像的更为普遍。
MSalters 2011年

4
这让我想起了一个引语The Design and Evolution of C++这是这一概念的起源,这些概念多年来已发展成为C ++设计的经验法则:用户定义类型和内置类型应相对于语言规则表现出相同的行为并接受该语言及其相关工具提供了相同程度的支持。当理想化被制定时,内置类型得到了最好的支持,但是C ++超越了这个目标,因此与用户定义类型相比,内置类型现在获得的支持稍差。
塞斯·卡内基

11
@MSalters是的,但实际上cout确实返回一个左值(它本身并通过引用返回*this),而不是按值返回。所以有点不同。
塞斯·卡内基

5
@MSalters:ostream :: operator <<,就像operator =都返回参考,即L值。
约翰,

4
@MSalters In中std::cout << "duh" << std::endloperator<<所有返回非常量引用(均为左值),并且由于运算符都需要非常量引用作为其第一个参数,因此原始std::ostream对象不能为右值。在其他示例中(我想这是问题的动机),非const函数是在临时函数上调用的。
James Kanze 2011年

8

因为可以将结构分配给您,并且您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;
}

5
这个和这个之间有什么区别:int blah() { int f = 4; return f; } int main() { int a = 99; blah() = a; }结构有什么神奇之处吗?因为该代码无法编译。
塞斯·卡内基

@Seth见查尔斯·贝利的回答
乍得

2
Int没有成员函数,因此它们也没有int::operator=(int)。这就是关于结构的“神奇”之处:它们具有默认方法。
MSalters 2011年

@Seth,因为在您的示例中该函数返回一个int而不是一个对象,因此您无法为其分配任何内容。如果函数返回对int(int&)的引用,则可以为其分配一个引用。
布鲁诺

1
@bruno:s/nothing/anything/
Lightness比赛将于

7

一个有趣的应用程序:

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

...

f(g() += "</tag>");

在这里,g() +=修改临时项,这可能比创建其他临时项更快,+因为分配给g()返回值的堆可能已经具有足够的备用容量来容纳</tag>

看到它运行在ideone.com上,带有GCC / C ++ 11

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.