久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

C++中的RVO舉例分析

234次閱讀
沒有評論

共計 4142 個字符,預計需要花費 11 分鐘才能閱讀完成。

本篇內(nèi)容主要講解“C++ 中的 RVO 舉例分析”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓丸趣 TV 小編來帶大家學習“C++ 中的 RVO 舉例分析”吧!

前言

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

// 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++ 語言標準的規(guī)范強制性、多團隊多環(huán)境開發(fā)等等。

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

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

RVO

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

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

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

編譯環(huán)境:
AppleClang 10.0.1.10010046

* 第一種使用方式

HeavyObject func()
 return HeavyObject();
// call
HeavyObject o = func();

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

Constructor

Copy Constructor
Destructor
Destructor

但是實際運行后的輸出結(jié)果卻為

Constructor

Destructor

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

于是我反匯編了一下:

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() 函數(shù),__ZN11HeavyObjectC1Ev 即 HeavyObject::HeavyObject()。
不同編譯器的 C ++ 修飾規(guī)則略有不同。

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

* 第二種使用方式

HeavyObject func()
 HeavyObject o;
 return o;
// call
HeavyObject o = func();

運行上述調(diào)用代碼的結(jié)果為

Constructor

Destructor

與第一種使用方式的結(jié)果相同,這里編譯器實際做了 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)

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

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

* 第三種使用方式

HeavyObject dummy(int index)
 HeavyObject o[2];
 return o[index];
// call
HeavyObject o = dummy(1);

運行后的結(jié)果為

Constructor

Constructor
Copy Constructor
Destructor
Destructor
Destructor

從運行的結(jié)果可以看到?jīng)]有做 RVO 優(yōu)化,此時調(diào)用了拷貝構(gòu)造函數(shù)。

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

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

*  第四種使用方式

HeavyObject func()
 return std::move(HeavyObject());
// call
HeavyObject o = func();

實際運行輸出的結(jié)果是

Constructor

Move Constructor
Destructor
Destructor

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

*  第五種使用方式

HeavyObject func()
 HeavyObject o;
 return static_cast HeavyObject 
// call
HeavyObject o = func();

實際運行輸出的結(jié)果是

Constructor

Copy Constructor
Destructor
Destructor

上述的函數(shù)實現(xiàn)直接返回本地對象的引用,實際運行結(jié)果仍然調(diào)用了拷貝構(gòu)造函數(shù),并不是期望的只調(diào)用一次構(gòu)造和析構(gòu)函數(shù),也就是說編譯器并沒有做 RVO。

到此,相信大家對“C++ 中的 RVO 舉例分析”有了更深的了解,不妨來實際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

正文完
 
丸趣
版權聲明:本站原創(chuàng)文章,由 丸趣 2023-08-17發(fā)表,共計4142字。
轉(zhuǎn)載說明:除特殊說明外本站除技術相關以外文章皆由網(wǎng)絡搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 淳安县| 冕宁县| 英德市| 会东县| 酒泉市| 合阳县| 宾川县| 冷水江市| 汉源县| 南康市| 大荔县| 乐清市| 辛集市| 巴南区| 双辽市| 宣恩县| 富川| 济阳县| 故城县| 灌南县| 若尔盖县| 勐海县| 车险| 读书| 武川县| 灌南县| 大兴区| 彰化市| 嘉峪关市| 正蓝旗| 朝阳区| 万州区| 民县| 衡水市| 尼木县| 麻城市| 房产| 团风县| 平凉市| 祁阳县| 林甸县|