作者holymars ()
看板C_and_CPP
標題Re: RVO vs inline
時間Tue Oct 20 15:55:57 2009
※ 引述《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