看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《littleshan (我要加入劍道社!)》之銘言: : 推 holymars:RVO是因為Compiler會把return value當成參數傳進function 10/20 13:02 : → holymars:裡才會有的optimization吧..如果函式本身inline 10/20 13:03 : → holymars:就不用把return value放在參數列上,自然也不會進行 10/20 13:03 : → holymars:RVO啊.. 10/20 13:03 : RVO 牽涉的不只是 implementation detail : 它也會影響到語意 : 因為它是直接「消除」掉 copy-constructor/assignment : 即使這個 copy-constructor 具有 side effect 也是一樣會被省略 : 但 inline function 是不能影響語意的 : compiler 不能因為 inline function 就自動省略該有的 constructor/assignment : 除非說 copy-constructor/assignment 是 compiler 自己產生所以它知道內容 RVO和NRVO是不一樣的 RVO也是不能影響語意的 根據inside the C++ object model的說法 最先提出RVO的Jonathan Shopiro,是因為Compiler會把return value傳進參數列 並且在return之前隱含產生一個copy constructor RVO是為了省掉compiler自動產生的這個copy constructor所發展的技術 它省掉的是compiler產生的,所以並不影響語意 書中舉例是像這樣: Foo bar() { Foo f; //中間對f做了某些操作 return f; } 這樣的code會被compiler轉化成 void bar(Foo& __ret) { Foo f; //中間對f做了某些操作 __ret.Foo::Foo(f); // 在這裡呼叫copy ctor return; } Jonathan Shopiro想省掉那個compiler自動產生的copy ctor 所以希望return value在函式中匿名存在 把所有的操作移到另一個constructor去進行 Foo bar() { return Foo(bulabula...); // 這是個「計算用的constructor」,用來取代前述的「對f做了某些操作」 } 這樣Compiler就不會去呼叫那個隱含的Copy constructor 而是會直接在return的時侯調用那個「計算用」的copy ctor 以上是RVO NRVO就像原文所說的 即使return value不是匿名,而是一個具名(Named)的變數 一樣透過Compiler優化把那個具名的變數取代成Compiler 傳進去的__ret 這樣它不止省掉了那個原本會隱式產生的copy ctor 連帶的copy ctor的side effect也從local variable轉移到了return value上面 而且這個side effect有可能不只是printf("xxxx\n")而已 舉下面的例子來說 inline Foo bar(const Foo& f) { Foo tmp = f; // copy constructor return tmp; } 使用者原本預期 Foo tmp = f; 這行copy-initialization會對tmp產生某些side effect 但是不會影響到return回去的值的行為 這聽起來很吊詭 但是是有可能的 因為Compiler調用的 __ret.Foo::Foo(tmp); 是direct-initialization 雖然在大多數情況下這兩種initilzation會invoke同樣的constructor Compiler幫你把原本預期對tmp做的copy-initialization 變成對__ret做了 上面是解釋RVO和NRV的不同 然後來看看原本的問題 : List Test::GetList() : { : return m_oList; : } : : List oList = oTest.GetList(); 嗯..其實這個函式既沒有動用到RVO,也沒有動用到NRV 我用VC8.1 compile了一個完全不optimize的asm 就算是完全沒有optimize的版本 也只在function裡面做了一次copy ctor (compiler預設產生的那個) 結果Debug版的pseudo code長得像這樣: (函數名稱是打亂過的,我用__GetList代替) void __GetList(List& oTest, List& oList) { //前面一堆stack處理 oList.List::List(oTest.m_oList); //後面一堆stack處理 } List oTest; List oList; __GetList(oTest, oList) 也就是說 真正被省掉的東西是function「外面」的那個copy constructor 另外 如果寫成 : List Test::GetList() : { : return m_oList; : } : : List oList; : oList = oTest.GetList(); 就會出現copy ctor -> copy assignment -> copy ctor這樣的呼叫順序 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 114.32.15.163
holymars:最後那個copy ctor是因為copy assignment傳回的不是 10/20 16:07
holymars:List&而是List 所以又產生一個隱形的copy ctor.. 10/20 16:07