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

时间:2011-11-22 14:51:31

标签: 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()堆栈上创建一个本地LargeObject glo,然后在返回时将它分配给test()中的tlo,这涉及一个复制操作。

但是,聪明的编译器是否应该意识到glo将被分配给tlo,因此只是首先使用tlo的内存来避免复制操作?导致某些事情(功能上)如:

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

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

我的猜测是,编译器会做类似的事情。但是它总能完成吗?有没有像这样优化的情况?如何知道我的返回值是否被复制?

3 个答案:

答案 0 :(得分:4)

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

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

无法在那里完成,因为编译器无法知道它是否会使用glo1或glo2作为返回值。

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

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

对于初学者来说,即使是天真的编译器也不会“分配给 tlo“,因为标准不允许它。形式语义 您的代码涉及两个副本(都使用复制构造函数);该 首先从glo到临时返回值,第二个从此返回 临时返回值tlo。然而,标准正式给出了 编译器有权消除这两个副本,具体如下 案例,实际上,我想所有编译器都会这样做。

每次返回局部变量时,都可以抑制第一个副本 暂时的;如果存在多个编译器,则某些编译器不会执行此操作 但是,代码中return(但情况永远不会如此) 书面代码)。

第二个副本的抑制取决于你的事实 在呼叫站点构建新对象。如果你没有建造 一个新的对象,然后甚至可能没有第二个副本来压制;例如 在像getLargeObject().memberFunction()这样的情况下。如果你要分配 然而,对于现有的对象,编译器可以做的事情并不多;它 必须调用赋值运算符。如果赋值运算符复制, 然后你得到那份副本。