作者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
1F:→ holymars:最後那个copy ctor是因为copy assignment传回的不是 10/20 16:07
2F:→ holymars:List&而是List 所以又产生一个隐形的copy ctor.. 10/20 16:07