ムーブセマンティクスについて調べたところで自分が気になったこと。
BigFatMovableObject GetBigFatMovableObject() { BigFatMovableObject mo; return mo; } int main() { BigFatMovableObject mo(GetBigFatMovableObject()); return 0; }
最初、自分はこれが適切なムーブコンストラクタを呼び出す、
と考えたんだけど、(どうみてもコピーよりムーブの方がコストが安い状況なので)
実際にはNRVOといった最適化によって、
「移動もコピーも発生しない状況」にされてしまった訳です。
これは駄目だ!!
と思ってvectorでやったわけですが、
意外とNRVO(RVO)って知られてないこともあるのかと思い書いてみる次第。
NRVOとは、
名前付き戻り値の最適化 (Named Return Value Optimization)
というやつです。
RVOは名前付きでないものです。
BigFatMovableObject GetBigFatMovableObject() { BigFatMovableObject mo; // 名前付き! return mo; }
BigFatMovableObject GetBigFatMovableObject() { return BigFatMovableObject(); // 名前なし! }
というような感じです。
こいつをどう最適化するかと言うと、
この最適化が働くと、「無用だと思われるコピーコンストラクタ」をコンパイラが削除します。
(削除しても「いいよ」ということになっています)
要するに「値返し」をするさいに本来であれば
1.関数内のオブジェクトの生成/破棄
2.戻り値のオブジェクトの生成(関数内のオブジェクトからのコピー)/破棄
3.関数を呼び出した側のオブジェクトの生成(戻り値のオブジェクトからのコピー)
といった無駄な手順を幾度も踏まなければなりませんが、
「このコピーって無駄だよね?」って感じでコンパイラが省いてくれるものです。
値返しのコピーコストが高いと思って使ってない人もコンパイラによってはちゃんとこれが働いてくれるので値返しでも構わないケースも多々あります。
(ただし規約で必ずそうしなければならない、というわけではなく、
「してもいいよ」なのでコンパイラ実装依存です)
NRVOが働かない状況だとこうなります。
C++ code
- 49 lines - codepad
----- GetBigFatMovableObject ----- BigFatMovableObject::BigFatMovableObject() Address:0x8051438
関数の中でオブジェクトが生成されました。
BigFatMovableObject(const BigFatMovableObject& rhs) Address:0x805b1a0 BigFatMovableObject::~BigFatMovableObject() Address:0x8051438
戻り値に対するコピー開始。
戻り値に関するコピーが終了したので関数内オブジェクト破棄。
BigFatMovableObject(const BigFatMovableObject& rhs) Address:0x8051438 BigFatMovableObject::~BigFatMovableObject() Address:0x805b1a0
main関数内のオブジェクトにコピー。
戻り値が破棄される。
----- main end. ----- BigFatMovableObject::~BigFatMovableObject() Address:0x8051438
main関数内オブジェクト破棄。
で、これはかなり悲惨な状況ですが、
gcc4.4だと
----- GetBigFatMovableObject ----- BigFatMovableObject::BigFatMovableObject() Address:0x100801000 ----- main end. ----- BigFatMovableObject::~BigFatMovableObject() Address:0x100801000
こうなっちゃうんですよね。
見事にコピーコンストラクタもムーブコンストラクタも省略されちゃいます。
なので、0xにちゃんと対応したようなコンパイラを使う場合、
こういうケースではムーブコンストラクタの出番はないかもしれません。