Visual C++ 2005中的命名返回值优化

 
Visual C++ 2005 中的命名返回值优化
 
 
内容:
NRVO 优化概述
示例代码
优化的局限性
优化的副作用
 
 
       多年来,Microsoft Visual C++编译器一直在努力寻求更新的技术与优化方式,以求最大可能地提高程序的性能。此文描述了Visual C++编译器在不同情况下,是怎样消除多余的复制构造函数和析构函数的。
 
       通常来说,当方法返回对象的一个实例时,会创建一个临时对象,并通过复制构造函数复制到目标对象中。在C++标准中,允许省略复制构造函数(哪怕会导致不同的程序行为),但这有一个副作用,就是编译器可能会把两个对象当成一个。Visual C++ 8.0(Visual C++ 2005)充分利用了C++标准的可伸缩性,加入了一些新的特性——命名返回值优化(NRVO)。NRVO消除了基于堆栈返回值的复制构造函数和析构函数,并去除了对多余复制构造函数和析构函数的调用,从而全面地提高了程序的性能。但要注意到,优化和未优化的代码,可能会有不同的程序行为表现。
       而在某些情况下,NRVO不会进行优化(参见优化的局限性一节),以下是一些常见的情况:
Ø 不同的路径返回不同的命名对象
Ø 引入EH状态的多重返回路径(甚至所有的路径中返回相同的命名对象)
Ø 由内联汇编语句引用的命名返回对象
 
 
NRVO 优化概述
以下是一个简单的示例,演示了优化是怎样被实现的:
 
A MyMethod (B &var)
{
   A retVal;
   retVal.member = var.value + bar(var);
   return retVal;
}
 
       使用上述函数的程序可能会有一个像如下所示的构造函数:
valA = MyMethod(valB);
 
       由MyMethod返回的值会创建在内存空间中,并通过隐藏的参数指向ValA。以下是函数中带有隐藏参数,并清晰地写明构造和析构函数时样子:
 
A MyMethod (A &_hiddenArg, B &var)
{
   A retVal;
   retVal.A::A();                                     // retVal的构造函数
   retVal.member = var.value + bar(var);
   _hiddenArg.A::A(retVal);                   // A的复制构造函数
   return;
retVal.A::~A();                                     // retVal的析构函数
}
 
       从以上代码中,很明显可看出有一些可以优化的地方。最基本的想法是消除基于堆栈的临时值(retVal),并使用隐藏参数,从而消除那些基于堆栈值的复制构造函数和析构函数。以下是NRVO优化过的代码:
 
A MyMethod(A &_hiddenArg, B &var)
{
   _hiddenArg.A::A();
   _hiddenArg.member = var.value + bar(var);
   Return
}
 
 
       示例代码
       Sample1.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");}
      ~RVO(){printf ("I am in destructor/n");}
      int mem_var;
};
RVO MyMethod (int i)
{
      RVO rvo;
      rvo.mem_var = i;
      return (rvo);
}
int main()
{
      RVO rvo;
      rvo=MyMethod(5);
}
 
       打开或关闭NRVO编译sample1.cpp,将会产生不同的程序行为。
 
       不带NRVO编译(cl /Od sample1.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选项编译(cl /O2 sample1.cpp),以下是输出内容:
I am in constructor
I am in constructor
I am in destructor
I am in destructor
 
 
       Sample2.cpp:较复杂一点的代码
 
#include <stdio.h>
class A {
 public:
    A() {printf ("A: I am in constructor/n");i = 1;}
    ~A() { printf ("A: I am in destructor/n"); i = 0;}
    A(const A& a) {printf ("A: I am in copy constructor/n"); i = a.i;}
    int i, x, w;
};
class B {
 public:
    A a;
    B() { printf ("B: I am in constructor/n");}
    ~B() { printf ("B: I am in destructor/n");}
    B(const B& b) { printf ("B: I am in copy constructor/n");}
};
A MyMethod()
{
    B* b = new B();
    A a = b->a;
    delete b;
    return (a);
}
int main()
{
    A a;
    a = MyMethod();
}
 
不带NRVO(cl /Od sample2.cpp)的输出如下:
A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in copy constructor
A: I am in destructor
A: I am in destructor
A: I am in destructor
 
当打开NRVO优化时,输出如下:
A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in destructor
A: I am in destructor
 
 
       优化的局限性
       在某些情况下,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)消除了多余的函数调用,从而在一定程度上提高了程序的速度,需记住的是,优化有时也会有副作用,请谨慎使用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值