Visual C++ 2005中的命名返回值优化
优化的局限性
在某些情况下,NRVO优化不会起作用,以下是存在优化局限性的一些示例程序。
Sample3.cpp:含有例外的代码
在例外(Exception)情况中,隐藏参数必须在它被替换的临时范围内析构。
//RVO类定义在sample1.cpp中
#include <stdio.h>
RVO MyMethod (int i)
{
RVO rvo;
rvo.mem_var = i;
throw "I am throwing an exception!";
return (rvo);
}
int main()
{
RVO rvo;
try
{
rvo=MyMethod(5);
}
catch (char* str)
{
printf ("I caught the exception\n");
}
}
不带NRVO编译(cl /Od /EHsc sample3.cpp),输出如下:
I am in constructor
I am in constructor
I am in destructor
I caught the exception
I am in destructor
如果注释掉“throw”语句,输出将会如下如示:
I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
现在,如果注释掉“throw”语句,并且用NRVO编译,程序输出如下:
I am in constructor
I am in constructor
I am in destructor
I am in destructor
这就是说,不管打开或关闭NRVO选项,sample3.cpp的程序行为都一样。
Sample4.cpp:不同的命名对象
想要充分利用优化,所有的返回路径必须都返回相同的命名对象,请看如下示例代码:
#include <stdio.h>
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo;
rvo.mem_var = i;
if (rvo.mem_var == 10)
return (RVO());
return (rvo);
}
int main()
{
RVO rvo;
rvo=MyMethod(5);
}
优化打开时(cl /O2 sample4.cpp)的输出,与没有进行任何优化时(cl /Od sample.cpp)的输出是一样的。因为不是所有的返回路径都返回同一命名对象,所以NRVO此时不起任何作用。
I am in constructor
I am in constructor
I am in copy constructor
如果把上述代码的所有返回路径都改为返回rvo(如下例Sample4_modified.cpp),此时优化就会消除多余的复制构造函数。
经过修改的Sample4_Modified.cpp,以利用NRVO。
#include <stdio.h>
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo;
if (i==10)
return (rvo);
rvo.mem_var = i;
return (rvo);
}
int main()
{
RVO rvo;
rvo=MyMethod(5);
}
此时(cl /O2 Sample4_Modified.cpp)的输出:
I am in constructor
I am in constructor
Sample5.cpp:EH限制
以下的Sample5与Sample4基本一致,除了增加了RVO类的析构函数,并具有多重返回路径,且引入的析构函数在函数中创建了一个EH状态。由于编译器跟踪的复杂性,此类对象通常需要被析构,但它阻止了返回值优化,这也是Visual C++ 2005在将来需要改进的地方。
//RVO类定义在sample1.cpp中
#include <stdio.h>
RVO MyMethod (int i)
{
RVO rvo;
if (i==10)
return (rvo);
rvo.mem_var = i;
return (rvo);
}
int main()
{
RVO rvo;
rvo=MyMethod(5);
}
不论打开或关闭优化,Sample5.cpp都会产生相同的结果。
I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
要想打开NRVO优化,必须消除多重返回点,可以像如下所示修改MyMethod:
RVO MyMethod (int i)
{
RVO rvo;
if (i!=10)
rvo.mem_var = i;
return(rvo);
}
Sample6.cpp:内联汇编限制
当命名返回对象被内联汇编语句所引用时,编译器不会进行NRVO优化,请看下例代码:
#include <stdio.h>
// RVO类定义在sample1.cpp中
RVO MyMethod (int i)
{
RVO rvo;
__asm {
mov eax,rvo //可以注释掉此行
mov rvo,eax //可以注释掉此行
}
return (rvo);
}
int main()
{
RVO rvo;
rvo=MyMethod(5);
}
即使打开优化选项(cl /O2 sample6.cpp)来编译sample6.cpp,NRVO也不会起作用。这是因为内联汇编语句中引用了返回对象,因此,打开或关闭优化选项,输出都会像如下所示:
I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor
从以上输出,可清楚地看到,复制构造函数和析构函数并没有被消除。但如果注释掉汇编语句,优化将会消除掉这些函数调用。
优化的副作用
程序员必须意识到,如此之类的优化将会影响到程序的流程,以下的示例代码演示了优化所带来的影响。
Sample7.cpp
![]()
#include <stdio.h>
![]()
int NumConsCalls=0;
![]()
int NumCpyConsCalls=0;
![]()
class RVO
![]()
...{
![]()
public:
![]()
RVO()...{NumConsCalls++;}
![]()
RVO (const RVO& c_RVO) ...{NumCpyConsCalls++;}
![]()
};
![]()
RVO MyMethod ()
![]()
...{
![]()
RVO rvo;
![]()
return (rvo);
![]()
}
![]()
void main()
![]()
...{
![]()
RVO rvo;
![]()
rvo=MyMethod();
![]()
int Division = NumConsCalls / NumCpyConsCalls;
![]()
printf ("Constructor calls / Copy constructor calls = %d\n",Division);
![]()
}
![]()
不带优化选项编译sample7.cpp(cl /Od sample7.cpp),程序的输出是在意料之中的,构造函数被调用了两次,而复制构造函数被调用了一次,因此,输出的结果为2。
Constructor calls / Copy constructor calls = 2
另一方面,如果打开优化选项编译上述代码(cl /O2 sample7.cpp),NRVO将会起作用,并消除掉复制构造函数,因此NumCpyConsCalls结果为零,从而引发程序的除零错误。如果像sample7.cpp中那样没有很好地处理这种例外错误,将会导致程序崩溃。
从以上可看出,命名返回值优化(NRVO)消除了多余的函数调用,从而在一定程度上提高了程序的速度,需记住的是,优化有时也会有副作用,请谨慎使用。
