C++ 命名返回值優化(NRVO)

命名的返回值優化(NRVO),這優化了冗餘拷貝構造函數和析構函數調用,從而提高了總體性能。值得注意的是,這可能導致優化和非優化程序之間的不同行爲。

下面是代碼段1中的一個簡單示例,以說明優化及其實現方式:

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(); //constructor for retVal
    retVal.member = var.value + var(var);
    _hiddenArg.A::A(retVal); // the copy constructor for A
    return;
return.A::~A(); // destructor for retVal
}

上段代碼爲不使用NRVO的隱藏參數代碼(僞代碼)
從上面的代碼可以看出,有一些優化的機會。其基本思想是消除基於堆棧的臨時值(retVal)並使用隱藏的參數。因此,這將消除基於堆棧的值的拷貝構造函數和析構函數。下面是基於NRVO的優化代碼:

A MyMethod(A &_hiddenArg, B &var)
{
    _hiddenArg.A::A();
    _hiddenArg.member = var.value + bar(var);
    Resurn
}

帶有NRVO的隱藏參數代碼(僞代碼)

代碼示例
示例1:簡單示例

#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);
}

代碼:Sample1.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

使用NRVO,預期的輸出將是:

I am in constructor
I am in constructor
I am in destructor
I am in destructor

示例2:更復雜的示例

#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();
}

代碼Sample2.cpp
無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 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

優化限制
有些情況下,優化不會真正啓動。以下是這些限制的樣本
示例3:異常示例
在遇到異常時,隱藏的參數必須在它正在替換的臨時範圍內被破壞。

// RVO class is defined above in figure 4
#include <stdio.h>
RVO MyMethod(int i)
{
    RVO rvo;
    cvo.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");
    }

代碼Sample3.cpp
如果沒有NRVO,預期的輸出將是:

I am in constructor
I am in constructor
I am in destructor
I caught the exception
I am in destructor

如果“拋出”被註釋掉,輸出將是:

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被觸發,輸出將如下所示:

I am in constructor
I am in constructor
I am in destructor
I am in destructor

也就是說sample3.cpp在沒有NRVO的情況下,會表現出相同的行爲。

示例4:不同的命名對象示例
若要使用優化,所有退出路徑必須返回同一命名對象。

#include <stdio.h>
class RVO
{
public:
        RVO()
        {
                printf("I am in construct\n");
        }

        RVO(const RVO& c_RVO)
        {
                printf("I am in copy construct\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);
}

代碼Sample4.cpp
啓用優化時輸出與不啓用任何優化相同。NRVO實際上並不發生,因爲並非所有返回都返回相同的對象。

I am in constructor
I am in constructor
I am in copy constructor

如果將上面的示例更改爲返回rvo。在返回對象時,優化將消除複製構造函數:

#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);
}

代碼Sample4_Modified.cpp修改並使用NRVO,輸出結果將如下所示:

I am in constructor
I am in constructor

優化副作用
程序員應該意識到這種優化可能會影響應用程序的流程。下面的示例說明了這種副作用:

#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;
}

int main()
{
        RVO rvo;
        rvo = MyMethod();
        int Division = NumConsCalls / NumCpyConsCalls;
        printf("Construct calls / Copy constructor calls = %d\n", Division);
}

代碼段Sample5.cpp
編譯未啓用優化將產生大多數用戶所期望的。“構造函數”被調用兩次。“拷貝構造函數”被調用一次。因此除法生成2。

Constructor calls / Copy constructor calls = 2

另一方面,如果上面的代碼通過啓用優化進行編譯,NRVO將會啓用。因此“拷貝構造函數”調用將被刪除。因此,NumCpyConsCalls將爲零,導致異常。如果沒有適當處理,可以導致應用程序崩潰。


引入自:https://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章