C++傳回值最佳化

來源:互聯網
上載者:User

標籤:-o   style   assign   variant   ++   cte   void   機制   compiler   

傳回值最佳化(Return Value Optimization,簡稱RVO)是一種編譯器最佳化機制:當函數需要返回一個對象的時候,如果自己建立一個臨時對象用於返回,那麼這個臨時對象會消耗一個建構函式(Constructor)的調用、一個複製建構函式的調用(Copy Constructor)以及一個解構函式(Destructor)的調用的代價。

經過傳回值最佳化,就可以將成本降低到一個建構函式的代價。這樣就省去了一次拷貝建構函式的調用和依次解構函式的調用。

 

例子如下:

class MyString {public:     MyString() {         _data = NULL;         _len = 0;         printf("Constructor is called!\n");    }     MyString(const char* p) {         _len = strlen (p);         _init_data(p);         cout << "Constructor is called! this->_data: " << (long)_data << endl;    }     MyString(const MyString& str) {         _len = str._len;         _init_data(str._data);         cout << "Copy Constructor is called! src: " << (long)str._data << " dst: " << (long)_data << endl;    }        ~MyString() {         if (_data)        {            cout << "DeConstructor is called! this->_data: " << (long)_data << endl;             free(_data);        }        else        {            std::cout << "DeConstructor is called!" << std::endl;         }    }     MyString& operator=(const MyString& str) {         if (this != &str) {             _len = str._len;             _init_data(str._data);         }         cout << "Copy Assignment is called! src: " << (long)str._data << " dst" << (long)_data << endl;         return *this;     }         operator const char *() const {        return _data;    }    void display() const    {        if (_data)        {            cout << "str is " << _data << "(" << (long)_data << ")" << endl;        }        else        {            cout << "nothing" << endl;        }    }private:     char *_data;     size_t   _len;     void _init_data(const char *s) {         _data = new char[_len+1];         memcpy(_data, s, _len);         _data[_len] = ‘\0‘;     } }; MyString foo1(){    return MyString("123");}MyString foo2(){    MyString str1("456");    return str1;}int main(){    foo1();    cout << "--------------------\n";    foo2();    cout << "--------------------\n";    MyString str1 = foo1();    cout << "--------------------\n";    MyString str2 = foo2();    cout << "--------------------\n";    return 0;}

函數foo1直接返回一個臨時對象,而foo2返回一個局部變數。在沒有RVO的情況下,不管是調用foo1還是foo2,實際上都是先調用建構函式,然後調用複製建構函式構造作為傳回值的臨時對象。而對於str1和str2的構造,還會再次調用一次複製建構函式。上述代碼,使用的編譯命令為:g++ -fno-elide-constructors -o rvo rvo.cpp

-fno-elide-constructors選項可以取消編譯器的 copy-elision 最佳化策略。得到的結果如下:

 

Constructor is called! this->_data: 8949776

Copy Constructor is called! src: 8949776 dst: 8949808

DeConstructor is called! this->_data: 8949776

DeConstructor is called! this->_data: 8949808

--------------------

Constructor is called! this->_data: 8949808

Copy Constructor is called! src: 8949808 dst: 8949776

DeConstructor is called! this->_data: 8949808

DeConstructor is called! this->_data: 8949776

--------------------

Constructor is called! this->_data: 8949776

Copy Constructor is called! src: 8949776 dst: 8949808

DeConstructor is called! this->_data: 8949776

Copy Constructor is called! src: 8949808 dst: 8949776

DeConstructor is called! this->_data: 8949808

--------------------

Constructor is called! this->_data: 8949808

Copy Constructor is called! src: 8949808 dst: 8949840

DeConstructor is called! this->_data: 8949808

Copy Constructor is called! src: 8949840 dst: 8949808

DeConstructor is called! this->_data: 8949840

--------------------

DeConstructor is called! this->_data: 8949808

DeConstructor is called! this->_data: 8949776

 

如果編譯時間去掉了-fno-elide-constructors選項,則編譯器開啟RVO,結果如下:

 

Constructor is called! this->_data: 34054160

DeConstructor is called! this->_data: 34054160

--------------------

Constructor is called! this->_data: 34054160

DeConstructor is called! this->_data: 34054160

--------------------

Constructor is called! this->_data: 34054160

--------------------

Constructor is called! this->_data: 34054192

--------------------

DeConstructor is called! this->_data: 34054192

DeConstructor is called! this->_data: 34054160

 

可見開啟了RVO之後,省略了不必要的複製拷貝,開啟RVO之後,函數是直接在接收傳回值的地方直接構造對象。

 

實際上,foo1和foo2分別對應了RVO和NRVO(Named Return Value Optimization)。具名傳回值最佳化(NRVO),是對於按值返回“具名對象”(就是有名字的變數)時的最佳化手段,其實道理是一樣的,但由於返回的值是具名變數,情況會複雜很多。所以,能執行最佳化的條件更苛刻。比如函數中,在不同的返迴路徑上返回不同名的對象,就不會執行NRVO。

比如下面的代碼:

MyString bar1(int n){    if (n > 2)    {        return MyString("abc");    }    else    {        return MyString("ABC");    }}MyString bar2(int n){    MyString str1("abc");    MyString str2("ABC");    if (n > 2)    {        return str1;    }    else    {        return str2;    }}int main(int argc, char **argv){    bar1(1);    cout << "--------------------\n";    bar2(1);    cout << "--------------------\n";    MyString str1 = bar1(1);    cout << "--------------------\n";    MyString str2 = bar2(1);    cout << "--------------------\n";    return 0;}

函數bar1返回臨時對象,bar2返回具名對象,也就是說,如果執行最佳化的話,bar1執行RVO,而bar2執行NRVO。

首先是加上-fno-elide-constructors選項後的運行結果:

 

Constructor is called! this->_data: 11149328

Copy Constructor is called! src: 11149328 dst: 11149360

DeConstructor is called! this->_data: 11149328

DeConstructor is called! this->_data: 11149360

--------------------

Constructor is called! this->_data: 11149360

Constructor is called! this->_data: 11149328

Copy Constructor is called! src: 11149328 dst: 11149392

DeConstructor is called! this->_data: 11149328

DeConstructor is called! this->_data: 11149360

DeConstructor is called! this->_data: 11149392

--------------------

Constructor is called! this->_data: 11149392

Copy Constructor is called! src: 11149392 dst: 11149360

DeConstructor is called! this->_data: 11149392

Copy Constructor is called! src: 11149360 dst: 11149392

DeConstructor is called! this->_data: 11149360

--------------------

Constructor is called! this->_data: 11149360

Constructor is called! this->_data: 11149328

Copy Constructor is called! src: 11149328 dst: 11149424

DeConstructor is called! this->_data: 11149328

DeConstructor is called! this->_data: 11149360

Copy Constructor is called! src: 11149424 dst: 11149360

DeConstructor is called! this->_data: 11149424

--------------------

DeConstructor is called! this->_data: 11149360

DeConstructor is called! this->_data: 11149392

 

加上-fno-elide-constructors選項後,運行結果如下:

 

Constructor is called! this->_data: 9449488

DeConstructor is called! this->_data: 9449488

--------------------

Constructor is called! this->_data: 9449488

Constructor is called! this->_data: 9449520

Copy Constructor is called! src: 9449520 dst: 9449552

DeConstructor is called! this->_data: 9449520

DeConstructor is called! this->_data: 9449488

DeConstructor is called! this->_data: 9449552

--------------------

Constructor is called! this->_data: 9449552

--------------------

Constructor is called! this->_data: 9449488

Constructor is called! this->_data: 9449520

Copy Constructor is called! src: 9449520 dst: 9449584

DeConstructor is called! this->_data: 9449520

DeConstructor is called! this->_data: 9449488

--------------------

DeConstructor is called! this->_data: 9449584

DeConstructor is called! this->_data: 9449552

 

對比上面的結果,可見返回臨時對象的bar1函數的調用進行了最佳化。而bar2函數的調用,不管有沒有-fno-elide-constructors選項,單獨調用bar2返回結果都是一樣的,說明沒有執行NRVO。對比”MyString str2 = bar2(1);”語句的執行結果,發現加上-fno-elide-constructors選項選項之後,僅僅少了一次複製建構函式的調用,這是因為雖然bar2沒有執行NRVO,但是使用bar2返回的臨時對象初始化str2時,編譯器依然有copy elision的最佳化策略。

 

有關copy elision的解釋如下:

In C++ computer programming, copy elision refers to a compiler optimization technique that eliminates unnecessary copying of objects.

The standard also describes a few situations where copying can be eliminated even if this would alter the program‘s behavior, the most common being the return value optimization. Another widely implemented optimization, described in the C++ standard, is when a temporary object of class type is copied to an object of the same type.

(https://en.wikipedia.org/wiki/Copy_elision)

 

When a nameless temporary, not bound to any references, would be copied or moved (since C++11) into an object of the same type (ignoring top-level cv-qualification), the copy/move (since C++11) is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be copied or moved (since C++11) to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".

(http://en.cppreference.com/w/cpp/language/copy_elision)

 

註:以上所有代碼的編譯環境是:作業系統CentOS Linux release 7.3.1611;GCC版本:gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)

 

參考:

http://blog.csdn.net/gatieme/article/details/22650353

http://www.cnblogs.com/liyiwen/archive/2009/12/02/1615711.html

C++傳回值最佳化

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.