C++编译器是否在按值返回时避免复制?

问题描述:

考虑下面的代码:C++编译器是否在按值返回时避免复制?

LargeObject getLargeObject() 
{ 
    LargeObject glo; 
    // do some initialization stuff with glo 
    return glo; 
} 

void test() 
{ 
    LargeObject tlo = getLargeObject(); 
    // do sth. with tlo; 
} 

一个简单的编译器将创建getLargeObject()栈上的局部LargeObject GLO和然后将其指定在测试TLO返回时,它涉及到一个复制操作()。

但是不应该有一个聪明的编译器意识到glo将会被分配给tlo,因此只是首先使用tlo的内存来避免复制操作?导致一些(功能)像:

void getLargeObject(LargeObject &lo) 
{ 
    // do init stuff 
} 

void test() 
{ 
    LargeObject lo; 
    getLargeObject(lo); 
} 

我的猜测是,编译器做类似的事情。但它可以一直完成吗?有没有不能像这样优化的情况?我怎么知道我的返回值是否被复制?

+7

http://en.wikipedia.org/wiki/Return_value_optimization –

+0

@OliCharlesworth:这是一个答案! –

+3

指向文章的链接不是答案。 –

你的猜测是正确的。是的,有些情况下无法做到的,例如情况:

LargeObject getLargeObject() 
{ 
    LargeObject glo1, glo2; 
    // do some initialization stuff   
    if (rand() % 2) 
     return glo1; 
    return glo2; 
} 

它不能做的工作还有,因为编译器无法知道它是否会使用glo1或酮g102返回值。

“我怎么知道我的返回值是否被复制?”

我能想到的两种方法。你可以创建嘈杂的复制构造函数。也就是说,复制具有一些可检测副作用的构造函数,如打印消息。然后,当然这是对装配的旧看法。

+0

甚至有在这里可以这样做的情况。 RVO是许多平台上ABI的一部分,这意味着调用方保留返回变量的空间并传递地址。一个好的编译器会在可能的最近点在那个地方构造一个对象,并且在追溯执行路径时,在某些情况下(例如没有为此目的需要考虑的ctor的副作用),它可以首先检查如果,然后构造对象就地。无可否认,这些案例很少见,但编译器这几天也在改进。 – PlasmaHH

+1

更一般地说,如果函数中有多个'return',一些编译器将无法执行RVO。当然,如果在函数中同时使用'glo1'和'glo2',则需要两个对象,有时会返回一个,有时另一个,但编译器无法避免复制。 –

+0

重要的是要注意,詹姆斯坎泽在他的回答中提到了它,但它在这里不存在,实际上有两个副本,一个从gloX到返回的临时文件,另一个从临时文件到对象在'test'中构建。第二个副本是PlasmaHH提到的ABI中处理的内容,并且总是*执行(在我所知道的所有平台中),但第一个副本取决于编译器能够知道哪个glo1或glo2会在构造对象时返回(更多[here](http://definedbehavior.blogspot.com/2011/08/value-semantics-nrvo.html)) –

是的,它应该。这称为返回值优化(NRVO或RVO)。

+0

是的,在这种特殊情况下,它是NRVO,因为有一个返回的命名变量。 RVO是在创建实例并在返回语句中返回的时候。 –

对于初学者来说,即使是天真的编译器也不会将“分配给 tlo ”,因为标准不允许。代码的正式语义 涉及两个副本(都使用复制构造函数); 第一个从glo到一个临时返回值,第二个从这个 临时返回值到tlo。但是,该标准正式给出了编译器有权在这个特定的 的情况下消除这两个副本,实际上我想所有的编译器都这样做。

第一个副本可以在任何时候被抑制,只要你返回一个局部变量或 临时;但是,如果代码中存在多个 return,则某些编译器不会执行此操作(但是,写入代码的井 中绝不会出现这种情况)。

第二个副本的取消取决于您在呼叫站点构建一个新对象的事实 。如果你没有构建一个新的对象,那么甚至可能没有第二个副本来压制;例如在getLargeObject().memberFunction()等情况下为 。但是,如果您将 分配给现有的对象,则编译器可以执行的操作并不多;它 必须调用赋值运算符。如果分配操作员复制 ,那么您将获得该副本。

+0

哇,那里有一些有趣的信息!我不确定,但如果我把它弄好。第一句和最后一句似乎与我相矛盾。或者它只是一种语言的东西? – Ben

+0

@James:*但在编写完好的代码中永远不会有这样的情况* - 哇 - 没有政治! :) –

+0

@Ben问题是,在第一句话中,我使用_copy_来调用复制构造函数 - 我应该更加精确。在最后一句中,我以更一般的意义使用它:赋值运算符可能会“复制”大量数据。 (实际上,一个大型对象的典型赋值操作符可能会复制构造一个新实例,然后交换数据。) –