请考虑以下代码:
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);
}
我的猜测是,编译器会做类似的事情。但是它总能完成吗?有没有像这样优化的情况?如何知道我的返回值是否被复制?
答案 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()
这样的情况下。如果你要分配
然而,对于现有的对象,编译器可以做的事情并不多;它
必须调用赋值运算符。如果赋值运算符复制,
然后你得到那份副本。