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);
}
我的猜测是,编译器做类似的事情。但它可以一直完成吗?有没有不能像这样优化的情况?我怎么知道我的返回值是否被复制?
你的猜测是正确的。是的,有些情况下无法做到的,例如情况:
LargeObject getLargeObject()
{
LargeObject glo1, glo2;
// do some initialization stuff
if (rand() % 2)
return glo1;
return glo2;
}
它不能做的工作还有,因为编译器无法知道它是否会使用glo1或酮g102返回值。
“我怎么知道我的返回值是否被复制?”
我能想到的两种方法。你可以创建嘈杂的复制构造函数。也就是说,复制具有一些可检测副作用的构造函数,如打印消息。然后,当然这是对装配的旧看法。
甚至有在这里可以这样做的情况。 RVO是许多平台上ABI的一部分,这意味着调用方保留返回变量的空间并传递地址。一个好的编译器会在可能的最近点在那个地方构造一个对象,并且在追溯执行路径时,在某些情况下(例如没有为此目的需要考虑的ctor的副作用),它可以首先检查如果,然后构造对象就地。无可否认,这些案例很少见,但编译器这几天也在改进。 – PlasmaHH
更一般地说,如果函数中有多个'return',一些编译器将无法执行RVO。当然,如果在函数中同时使用'glo1'和'glo2',则需要两个对象,有时会返回一个,有时另一个,但编译器无法避免复制。 –
重要的是要注意,詹姆斯坎泽在他的回答中提到了它,但它在这里不存在,实际上有两个副本,一个从gloX到返回的临时文件,另一个从临时文件到对象在'test'中构建。第二个副本是PlasmaHH提到的ABI中处理的内容,并且总是*执行(在我所知道的所有平台中),但第一个副本取决于编译器能够知道哪个glo1或glo2会在构造对象时返回(更多[here](http://definedbehavior.blogspot.com/2011/08/value-semantics-nrvo.html)) –
是的,它应该。这称为返回值优化(NRVO或RVO)。
是的,在这种特殊情况下,它是NRVO,因为有一个返回的命名变量。 RVO是在创建实例并在返回语句中返回的时候。 –
对于初学者来说,即使是天真的编译器也不会将“分配给 tlo ”,因为标准不允许。代码的正式语义 涉及两个副本(都使用复制构造函数); 第一个从glo
到一个临时返回值,第二个从这个 临时返回值到tlo
。但是,该标准正式给出了编译器有权在这个特定的 的情况下消除这两个副本,实际上我想所有的编译器都这样做。
第一个副本可以在任何时候被抑制,只要你返回一个局部变量或 临时;但是,如果代码中存在多个 return
,则某些编译器不会执行此操作(但是,写入代码的井 中绝不会出现这种情况)。
第二个副本的取消取决于您在呼叫站点构建一个新对象的事实 。如果你没有构建一个新的对象,那么甚至可能没有第二个副本来压制;例如在getLargeObject().memberFunction()
等情况下为 。但是,如果您将 分配给现有的对象,则编译器可以执行的操作并不多;它 必须调用赋值运算符。如果分配操作员复制 ,那么您将获得该副本。
哇,那里有一些有趣的信息!我不确定,但如果我把它弄好。第一句和最后一句似乎与我相矛盾。或者它只是一种语言的东西? – Ben
@James:*但在编写完好的代码中永远不会有这样的情况* - 哇 - 没有政治! :) –
@Ben问题是,在第一句话中,我使用_copy_来调用复制构造函数 - 我应该更加精确。在最后一句中,我以更一般的意义使用它:赋值运算符可能会“复制”大量数据。 (实际上,一个大型对象的典型赋值操作符可能会复制构造一个新实例,然后交换数据。) –
http://en.wikipedia.org/wiki/Return_value_optimization –
@OliCharlesworth:这是一个答案! –
指向文章的链接不是答案。 –