亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么理解C++中的RVO

發布時間:2021-11-24 10:32:23 來源:億速云 閱讀:105 作者:iii 欄目:云計算

這篇文章主要講解了“怎么理解C++中的RVO”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么理解C++中的RVO”吧!

前言

考慮存在這樣一個類如HeavyObject,其拷貝賦值操作比較耗時,通常你在使用函數返回這個類的一個對象時會習慣使用哪一種方式?或者會根據具體場景選擇某一種方式?

// style 1
HeavyObject func(Args param);

// style 2
bool func(HeavyObject* ptr, Args param);

上面的兩種方式都能過到同樣的目的,但直觀上的使用體驗的差別也是非常明顯的:

style 1只需要一行代碼,而style 2需要兩行代碼

// style 1
HeavyObject obj = func(params);

// style 2
HeavyObject obj;
func(&obj, params);

但是,能達到同樣的目的,消耗的成本卻未必是一樣的,這取決于多個因素,比如編譯器支持的特性、C++語言標準的規范強制性、多團隊多環境開發等等。

看起來style 2雖然使用時需要寫兩行代碼,但函數內部的成本卻是確定的,只會取決于你當前的編譯器,外部即使采用不同的編譯器進行函數調用,也并不會有多余的時間開銷和穩定性問題。比如func內部使用clang+libc++編譯,外部調用的編譯環境為gcc+gnustl或者vc++,除了函數調用開銷,不用擔心其它性能開銷以及由于編譯環境不同會崩潰問題。

因此這里我主要剖析一下style 1背后開發者需要關注的點。

RVO

RVO是Return Value Optimization的縮寫,即返回值優化,NRVO就是具名的返回值優化,為RVO的一個變種,此特性從C++11開始支持,也就是說C++98、C++03都是沒有將此優化特性寫到標準中的,不過少量編譯器在開發過程中也會支持RVO優化(如IBM Compiler?),比如微軟是從Visual Studio 2010才開始支持的。

仍然以上述的HeavyObject類為例,為了更清晰的了解編譯器的行為,這里實現了構造/析構及拷貝構造、賦值操作、右值構造函數,如下

class HeavyObject
{
public:
    HeavyObject() { cout << "Constructor\n"; }
    ~HeavyObject() { cout << "Destructor\n"; }
    HeavyObject(HeavyObject const&) { cout << "Copy Constructor\n"; }
    HeavyObject& operator=(HeavyObject const&) { cout << "Assignment Operator\n"; return *this; }
    HeavyObject(HeavyObject&&) { cout << "Move Constructor\n"; }
private:
    // many members omitted...
};

編譯環境:
AppleClang 10.0.1.10010046

* 第一種使用方式

HeavyObject func()
{
    return HeavyObject();
}

// call
HeavyObject o = func();

按照以往對C++的理解,HeavyObject類的構造析構順序應該為

Constructor

Copy Constructor
Destructor
Destructor

但是實際運行后的輸出結果卻為

Constructor

Destructor

實際運行中少了一次拷貝構造和析構的開銷,編譯器幫助我們作了優化。

于是我反匯編了一下:

0000000100000f60 <__Z4funcv>:
   100000f60:    55                       push   %rbp
   100000f61:    48 89 e5                 mov    %rsp,%rbp
   100000f64:    48 83 ec 10              sub    $0x10,%rsp
   100000f68:    48 89 f8                 mov    %rdi,%rax
   100000f6b:    48 89 45 f8              mov    %rax,-0x8(%rbp)
   100000f6f:    e8 0c 00 00 00           callq  100000f80 <__ZN11HeavyObjectC1Ev>
   100000f74:    48 8b 45 f8              mov    -0x8(%rbp),%rax
   100000f78:    48 83 c4 10              add    $0x10,%rsp
   100000f7c:    5d                       pop    %rbp
   100000f7d:    c3                       retq   
   100000f7e:    66 90                    xchg   %ax,%ax

上述匯編代碼中的__Z4funcv即func()函數,__ZN11HeavyObjectC1Ev即HeavyObject::HeavyObject()。
不同編譯器的C++修飾規則略有不同。

實際上這里就是先創建外部的對象,再將外部對象的地址作為參數傳給函數func,類似style 2方式。

* 第二種使用方式

HeavyObject func()
{
    HeavyObject o;
    return o;
}

// call
HeavyObject o = func();

運行上述調用代碼的結果為

Constructor

Destructor

與第一種使用方式的結果相同,這里編譯器實際做了NRVO,來看一下反匯編

0000000100000f40 <__Z4funcv>: // func()
   100000f40:    55                       push   %rbp
   100000f41:    48 89 e5                 mov    %rsp,%rbp
   100000f44:    48 83 ec 20              sub    $0x20,%rsp
   100000f48:    48 89 f8                 mov    %rdi,%rax
   100000f4b:    c6 45 ff 00              movb   $0x0,-0x1(%rbp)
   100000f4f:    48 89 7d f0              mov    %rdi,-0x10(%rbp)
   100000f53:    48 89 45 e8              mov    %rax,-0x18(%rbp)
   100000f57:    e8 24 00 00 00           callq  100000f80 <__ZN11HeavyObjectC1Ev> // HeavyObject::HeavyObject()
   100000f5c:    c6 45 ff 01              movb   $0x1,-0x1(%rbp)
   100000f60:    f6 45 ff 01              testb  $0x1,-0x1(%rbp)
   100000f64:    0f 85 09 00 00 00        jne    100000f73 <__Z4funcv+0x33>
   100000f6a:    48 8b 7d f0              mov    -0x10(%rbp),%rdi
   100000f6e:    e8 2d 00 00 00           callq  100000fa0 <__ZN11HeavyObjectD1Ev> // HeavyObject::~HeavyObject()
   100000f73:    48 8b 45 e8              mov    -0x18(%rbp),%rax
   100000f77:    48 83 c4 20              add    $0x20,%rsp
   100000f7b:    5d                       pop    %rbp
   100000f7c:    c3                       retq   
   100000f7d:    0f 1f 00                 nopl   (%rax)

從上面的匯編代碼可以看到返回一個具名的本地對象時,編譯器優化操作如第一種使用方式一樣直接在外部對象的指針上執行構造函數,只是如果構造失敗時還會再調用析構函數。

以上兩種使用方式編譯器所做的優化非常相近,兩種方式的共同點都是返回本地的一個對象,那么當本地存在多個對象且需要根據條件選擇返回某個對象時結果會是如何呢?

* 第三種使用方式

HeavyObject dummy(int index)
{
    HeavyObject o[2];
    return o[index];
}

// call
HeavyObject o = dummy(1);

運行后的結果為

Constructor

Constructor
Copy Constructor
Destructor
Destructor
Destructor

從運行的結果可以看到沒有做RVO優化,此時調用了拷貝構造函數。

從上述三種實現方式可以看到,如果你的函數實現功能比較單一,比如只會對一個對象進行操作并返回時,編譯器會進行RVO優化;如果函數實現比較復雜,可能會涉及操作多個對象并不確定返回哪個對象時,編譯器將不做RVO優化,此時函數返回時會調用類的拷貝構造函數。

但是,當只存在一個本地對象時,編譯器一定會做RVO優化嗎?

* 第四種使用方式

HeavyObject func()
{
    return std::move(HeavyObject());
}

// call
HeavyObject o = func();

實際運行輸出的結果是

Constructor

Move Constructor
Destructor
Destructor

上述的函數實現直接返回臨時對象的右值引用,從實際的運行結果來看調用了Move構造函數,與第一種使用方式運行的結果明顯不同,并不是我期望的只調用一次構造函數和析構函數,也就是說編譯器沒有做RVO。

* 第五種使用方式

HeavyObject func()
{
    HeavyObject o;
    return static_cast<HeavyObject&>(o);
}

// call
HeavyObject o = func();

實際運行輸出的結果是

Constructor

Copy Constructor
Destructor
Destructor

上述的函數實現直接返回本地對象的引用,實際運行結果仍然調用了拷貝構造函數,并不是期望的只調用一次構造和析構函數,也就是說編譯器并沒有做RVO。

感謝各位的閱讀,以上就是“怎么理解C++中的RVO”的內容了,經過本文的學習后,相信大家對怎么理解C++中的RVO這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

丰台区| 连州市| 太湖县| 乌鲁木齐市| 始兴县| 巨野县| 沁水县| 衡水市| 疏附县| 隆昌县| 盘山县| 元朗区| 乐至县| 沧州市| 白玉县| 霍城县| 东至县| 江达县| 伊通| 泉州市| 靖西县| 渭源县| 静安区| 清涧县| 荃湾区| 拉孜县| 荣昌县| 县级市| 邛崃市| 如东县| 永丰县| 武邑县| 杭锦后旗| 巴里| 阿图什市| 靖边县| 镇原县| 孝感市| 浮山县| 大姚县| 桦川县|