うにゅぅ.id:ototoiさんのところで指摘されてた現象(id:ototoi:20041126#p1)が再現しない・・・.
#include <cstddef> template<class LHS, class RHS> class vector_plus { public: vector_plus(LHS const &lhs, RHS const &rhs) : lhs_(lhs), rhs_(rhs) { } double operator[](std::size_t idx) const { return lhs_[idx] + rhs_[idx]; } private: LHS const &lhs_; RHS const &rhs_; }; // trivially assignableなのでコピーコンストラクタと // 代入演算子の生成はコンパイラ任せ class vector { public: vector() : val_() { } template<class T> vector(T const &r) : val_() { val_[0] = r[0]; val_[1] = r[1]; val_[2] = r[2]; } template<class T> vector &operator=(T const &r) { val_[0] = r[0]; val_[1] = r[1]; val_[2] = r[2]; return *this; } double &operator[](std::size_t idx) { return val_[idx]; } double const &operator[](std::size_t idx) const { return val_[idx]; } private: double val_[3]; }; template<class RHS, class LHS> inline vector_plus<RHS, LHS> operator+(RHS const &rhs, LHS const &lhs) { return vector_plus<RHS, LHS>(rhs, lhs); } void f(vector &out, vector const &u, vector const &v, vector const &w) { out = u + v + w; } // 比較のベースラインとして用意した関数.fと処理内容一緒. void g(double *out, double const *p, double const *q, double const *r) { out[0] = p[0] + q[0] + r[0]; out[1] = p[1] + q[1] + r[1]; out[2] = p[2] + q[2] + r[2]; }
上のコードをVC++7.1 /Oxでコンパイルしたら以下のようになりました.
fのアセンブリ | gのアセンブリ(ベースライン) |
---|---|
|
|
gのアセンブリ with -O2 -O3も同じ (ベースライン) |
fのアセンブリ with -O2 | fのアセンブリ with -O3 |
---|---|---|
|
|
|
あと,式オブジェクトのobject generatorのoperator+のパラメータを値渡しにしちゃうと,CRTPによるオーバーロードの限定が(slicingするので)出来なくなるのはもちろん,「副作用のあるコンストラクタ・デストラクタを持った名前付きの自動変数は,たとえそれが使われないとしても最適化で削除しちゃダメよ.」という制限(規格の(3.7.2/1),これについてはExceptional C++の項目46やMC++Dの5.12にもちょっとだけ記述があります)のためにコンパイラがvectorオブジェクトと式オブジェクトを削除してくれなくなっちゃいます.
例えば上のコードでoperator+の定義を
template<class RHS, class LHS> inline vector_plus<RHS, LHS> operator+(RHS rhs, LHS lhs) { return vector_plus<RHS, LHS>(rhs, lhs); }
と変えただけで以下のようになっちゃいました.(他はさっきと全く同じ条件)
// VC++7.1 with /Oxでのfのアセンブリ void f(vector &out, vector const &u, vector const &v, vector const &w) { 00401070 sub esp,50h out = u + v + w; 00401073 mov eax,dword ptr [esp+5Ch] 00401077 mov ecx,dword ptr [eax] 00401079 mov edx,dword ptr [eax+4] 0040107C mov dword ptr [esp+3Ch],edx 00401080 mov dword ptr [esp+38h],ecx 00401084 mov ecx,dword ptr [eax+8] 00401087 mov edx,dword ptr [eax+0Ch] 0040108A mov dword ptr [esp+44h],edx 0040108E mov dword ptr [esp+40h],ecx 00401092 mov ecx,dword ptr [eax+10h] 00401095 mov edx,dword ptr [eax+14h] 00401098 mov eax,dword ptr [esp+58h] 0040109C mov dword ptr [esp+4Ch],edx 004010A0 mov dword ptr [esp+48h],ecx 004010A4 mov ecx,dword ptr [eax] 004010A6 mov edx,dword ptr [eax+4] 004010A9 mov dword ptr [esp+24h],edx 004010AD mov dword ptr [esp+20h],ecx 004010B1 mov ecx,dword ptr [eax+8] 004010B4 mov edx,dword ptr [eax+0Ch] 004010B7 mov dword ptr [esp+2Ch],edx 004010BB mov dword ptr [esp+28h],ecx 004010BF mov ecx,dword ptr [eax+10h] 004010C2 mov edx,dword ptr [eax+14h] 004010C5 mov dword ptr [esp+34h],edx 004010C9 mov edx,dword ptr [esp+60h] 004010CD push esi 004010CE mov esi,dword ptr [edx] 004010D0 mov dword ptr [esp+0Ch],esi 004010D4 mov esi,dword ptr [edx+4] 004010D7 mov dword ptr [esp+10h],esi 004010DB mov esi,dword ptr [edx+8] 004010DE mov dword ptr [esp+14h],esi 004010E2 mov esi,dword ptr [edx+0Ch] 004010E5 mov dword ptr [esp+18h],esi 004010E9 mov esi,dword ptr [edx+10h] 004010EC mov edx,dword ptr [edx+14h] 004010EF mov dword ptr [esp+34h],ecx 004010F3 mov dword ptr [esp+1Ch],esi 004010F7 mov dword ptr [esp+20h],edx 004010FB mov edx,dword ptr [esp+58h] 004010FF lea eax,[esp+24h] 00401103 lea ecx,[esp+3Ch] 00401107 mov dword ptr [esp+4],eax 0040110B mov dword ptr [esp+8],ecx 0040110F fld qword ptr [ecx] 00401111 fadd qword ptr [eax] 00401113 pop esi 00401114 fadd qword ptr [esp+8] 00401118 fstp qword ptr [edx] 0040111A fld qword ptr [ecx+8] 0040111D fadd qword ptr [eax+8] 00401120 fadd qword ptr [esp+10h] 00401124 fstp qword ptr [edx+8] 00401127 fld qword ptr [ecx+10h] 0040112A fadd qword ptr [eax+10h] 0040112D fadd qword ptr [esp+18h] 00401131 fstp qword ptr [edx+10h] } 00401134 add esp,50h 00401137 ret
GCCでも同様.
// GCC 3.2.2 with -O3でのfのアセンブリ pushl %ebp movl %esp, %ebp pushl %edi pushl %esi pushl %ebx subl $140, %esp movl 12(%ebp), %esi cld leal -56(%ebp), %edi movl $6, %ecx rep movsl movl 16(%ebp), %esi leal -88(%ebp), %edi movl $6, %ecx rep movsl leal -56(%ebp), %esi leal -88(%ebp), %ecx movl %esi, -128(%ebp) movl %ecx, -124(%ebp) movl 20(%ebp), %esi leal -120(%ebp), %edi movl $6, %ecx rep movsl fldl -88(%ebp) faddl -56(%ebp) movl 8(%ebp), %ebx faddl -120(%ebp) fstpl (%ebx) fldl -80(%ebp) faddl -48(%ebp) faddl -112(%ebp) fstpl 8(%ebx) fldl -72(%ebp) faddl -40(%ebp) faddl -104(%ebp) leal -120(%ebp), %ecx leal -128(%ebp), %edx fstpl 16(%ebx) movl %edx, -136(%ebp) movl %ecx, -132(%ebp) addl $140, %esp popl %ebx popl %esi popl %edi leave ret
というか,vectorクラスの実装の違いしかないはずなのに何でここまで結果が変わってくるんだろう?
#ありゃ.式オブジェクトが保持しているconst参照を値にしたらやっぱりオブジェクトの削除をしてくれなくなっちゃった.もしかしてメンバ変数も「副作用のあるコンストラクタ・デストラクタを持った名前付きの自動変数」に該当するのか?だとすると式オブジェクトは(const)参照持つ以外にやりようがないんじゃ・・・.
#ちぅかメンバ変数のstorage durationってどうなるのよ?
#っていうか,
3.7.4 Duration of sub-objects
1 The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (1.8).
そうか・・・なら(const)参照で持つ以外に方法がないな・・・ってΣ(゜Д゜;エーッ!?
#Expression Templateの際に中間的に生成される式オブジェクトのうち,以下のいずれかに該当するオブジェクトを最適化で削除することは規格で禁止されているため,そのようなオブジェクトの削除は絶対にありえない.
- 式オブジェクトを生成する関数のパラメータでctor/dtorに副作用のある型のオブジェクト(普通Restricted Name Injectionのために参照にするのでこれは自動的に外れる)
- 式オブジェクトが保持するデータメンバで,ctor/dtorに副作用のある型のオブジェクト
- (N)RVOが有効でない形で生成された式オブジェクト
ただし,上のどの条件にも当てはまらないオブジェクトが最適化によって削除されるかどうかはコンパイラ次第である.上の条件はあくまで「上の条件に該当するオブジェクト->削除されない」であって「上の条件に該当しない->削除される」ではない.
言語規格に厳密に従うとこういう結論になるんですけど・・・ホンマか,これ!?
#上の2番目を簡単に言うと,「式オブジェクトが値で式オブジェクトを持つと,その式オブジェクトが最適化で削除されることはない」になる.
#いやマテマテ.sub-objectのcomplete-objectにRVOが適応される場合はどないやねん!?
#ぁぁぁ・・・operator[]で使うんだった・・・もうダメポ・・・
#っていうかそもそも"side effect"ってどこまでの範囲を指してるのよ?
#うがー!!
#こりゃもうcomp.lang.c++.moderatedあたりに投げるしかないのか?