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

C++ 做C++;编译器在按值返回时避免复制?,c++,copy,return-value,compiler-optimization,C++,Copy,Return Value,Compiler Optimization,考虑以下代码: LargeObject getLargeObject() { LargeObject glo; // do some initialization stuff with glo return glo; } void test() { LargeObject tlo = getLargeObject(); // do sth. with tlo; } 一个简单的编译器将在getLargeObject()堆栈上创建一个本地LargeObje

考虑以下代码:

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

void test()
{
    LargeObject tlo = getLargeObject();
    // do sth. with tlo;
}
一个简单的编译器将在getLargeObject()堆栈上创建一个本地LargeObject glo,然后在返回时将其分配给test()中的tlo,这涉及到一个复制操作

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


我的猜测是,编译器也会做类似的事情。但这能永远做到吗?是否存在无法像这样优化的情况?我如何知道我的返回值是否被复制?

是,它应该被复制。这被称为命名返回值优化(NRVO或只是RVO)。

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

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

LargeObject getLargeObject()
{
    LargeObject glo1, glo2;
    // do some initialization stuff         
    if (rand() % 2)
        return glo1;
    return glo2;
}
因为编译器不知道是使用glo1还是glo2作为返回值,所以不能在那里执行

“我如何知道我的返回值是否被复制?”


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

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

LargeObject getLargeObject()
{
    LargeObject glo1, glo2;
    // do some initialization stuff         
    if (rand() % 2)
        return glo1;
    return glo2;
}
因为编译器不知道是使用glo1还是glo2作为返回值,所以不能在那里执行

“我如何知道我的返回值是否被复制?”


我可以想到两种方法。您可以创建嘈杂的复制构造函数。也就是说,复制具有一些可检测的副作用的构造函数,例如打印消息。当然还有对程序集的旧看法。

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

只要返回局部变量或 临时的;如果存在多个编译器,则某些编译器不会执行此操作
返回代码中的
(但在well中永远不会是这种情况) 书面代码)

第二个副本的抑制取决于您是否 在调用站点构造新对象。如果你不是在建造 一个新的对象,那么甚至可能没有第二个要抑制的副本;例如 在类似于
getLargeObject().memberFunction()
的情况下。如果你要分配 然而,对于现有对象,编译器所能做的并不多;信息技术 必须呼叫分配操作员。如果赋值运算符复制,
然后你就得到了副本。

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

只要返回局部变量或 临时的;如果存在多个编译器,则某些编译器不会执行此操作
返回代码中的
(但在well中永远不会是这种情况) 书面代码)

第二个副本的抑制取决于您是否 在调用站点构造新对象。如果你不是在建造 一个新的对象,那么甚至可能没有第二个要抑制的副本;例如 在类似于
getLargeObject().memberFunction()
的情况下。如果你要分配 然而,对于现有对象,编译器所能做的并不多;信息技术 必须呼叫分配操作员。如果赋值运算符复制,
然后你得到那份副本。

@OliCharlesworth:这是答案!链接到一篇文章并不是一个答案。-1:这个问题显然表明没有任何研究成果-这个话题在互联网上到处都有。@Ben-关于你的最后一个问题,请参阅:@OliCharlesworth:这是一个答案!一篇文章的链接并不是一个答案。-1:这个问题显然表明没有研究成果-这个主题在互联网上到处都有。@Ben-关于你的最后一个问题,请参阅:这里甚至有这样做的案例。RVO是许多平台上ABI的一部分,这意味着调用者保留返回变量的空间并传递地址。一个好的编译器会在可能的最晚点在该位置构造一个对象,并且在追溯执行路径时,在某些情况下(例如,没有需要考虑的ctor的副作用),它可以首先检查if,然后在适当的位置构造对象。诚然,这种情况很少见,但即使在这里,编译器现在也在改进。更一般地说,如果函数中有多个
return
,一些编译器将无法执行RVO。当然,如果在函数中同时使用
glo1
glo2
,则函数需要两个对象,有时返回一个,有时返回另一个,编译器无法避免复制。需要注意的是,James Kanze在他的回答中提到了这一点,但这里没有提到,实际上有两个副本,一个从
gloX
到返回的临时对象,另一个从临时对象到
test
中构造的对象。第二个副本是Plasmah提到的ABI中处理的,并且总是执行(在所有