オブジェクトの生成と破棄には莫大なコストがかかることがあります。
CPUによるメモリ割り当て、コンストラクタ・デストラクタの挙動等、オブジェクトが一つ生成され、破棄されるまでのコストは無視できません。あなたの目の届かないところで提供されたサードパーティ製のライブラリのクラスでは、コンストラクタ内で自分のメンバからCRC値を生成して勝手にツイートする実装になっているかもしれません。
コンパイラも、そんな我々の気持ちを汲んで、なるべく一時オブジェクトの生成を抑制=最適化しようとしてくれます。
そんな最適化の一つが今回説明するRVO(Return Value Optimization)です。
関数の戻り値を値で返す時に発生する一時オブジェクトをコンパイラは可能な限り消し去ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <cstdio> class Hoge { public: Hoge() { std::printf("c"); } Hoge(const Hoge& r) { std::printf("e"); } ~Hoge() { std::printf("d"); } }; Hoge create_hoge() { Hoge a; // a生成 return a; // 値返しの一時オブジェクト生成 } int main() { Hoge h = create_hoge(); // h生成 return 0; } |
一切なんの最適化も行われないと仮定すると上記のコードは3回Hogeオブジェクトを生成することになります。
しかし、実際には1度しかコンストラクタ・デストラクタは呼ばれません。
RVOが効く為です。
現代の主要なコンパイラにとってこの最適化は当たり前となっており、gccでもclangでも、armccでもRVOは効きます。
Wandboxで際に実行してみると、コンストラクタ・デストラクタがそれぞれ一度しか呼ばれないことが確認できます。
RVOによってコンパイラに生成されるコードの詳細についてはEfficient C++の第四章「戻り値の最適化」にも説明があります。
Named Constructor Idiom
この最適化を念頭に入れておけば、値を返す関数を可読性の高いコンストラクタ呼び出しとして使うことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// RGBカラーを表す構造体 struct Color { Color(float r, float g, float b) : red(r) , green(g) , blue(b) {} // 赤色のインスタンスを生成 static Color RED() { return Color(1, 0, 0); } // 緑色のインスタンスを生成 static Color GREEN() { return Color(0, 1, 0); } float red; float green; float blue; } int main() { Color font_color = Color::RED(); // 赤作りますよ Color bg_color = Color::GREEN(); // 緑作りますよ return 0; } |
これを Named Constructor Idiomと言います。
More C++ Idiomsにも載っていますので参照すると良いでしょう。
まとめ
- コンパイラは可能な限り一時オブジェクトの生成を抑制する
- 戻り値の最適化によって一時オブジェクトの生成が抑制されることをRVOという
- RVOを利用して余計なコストを気にせず可読性を上げる手法をNamed Constructor Idiomという