🧌

畏れずに値を返そう

2025/02/08に公開
2

以下のC++の戻り値のある関数を見てみましょう。

struct Data {
   std::string name;
   int age;
   long points;
};

Data createData() {
   return Data{ "Taro", 30, 1500 };   // コピーするかもしれない
}

int main() {
    Data data = createData();         // コピーするかもしれない
}

C++11前は言語のロジックとして、CreateData()の中で作られたDataのインスタンスが戻り値として返される時にコピーされて、main*()dataにもう一回コピーされるようになっていました。そういう余計なコピーを避けるために、多くのプログラマーは以下のように関数を書くようになりました。

void createData(Data& data) {
   data = {"Taro", 30, 1500};
}

int main() {
    Data data;
    createData(data);
}

このように出力引数を使うと、直接dataを変えられるため、余計なコピーは避けられます。参照型の代わりにポインタ型の引数が使われる関数も結構見かけます。

でもこういうことしないといけなかったのは90年代の話です。
C++11前でも殆どのコンパイラはReturn Value Optimization (RVO)という効率化を使って戻り値のコピーを避けてくれていました。
C++11でムーブコンストラクタが存在する限り、言語のロジックとして戻り値がコピーされずにムーブされるようになりました。
でも、もう安心してください!C++17でもうムーブコンストラクタがなくても戻り値がコピーされることは無くなりました。戻り値以外のケースでも余計なコピーは発生しなくなっています。

https://ja.cppreference.com/w/cpp/language/copy_elision
https://cpprefjp.github.io/lang/cpp17/guaranteed_copy_elision.html

なので、今時コピーを避けるために出力引数を使う必要はないです。どんなにでかい構造体でもコピーを恐れずに直接戻り値として返しましょう。


|cpp記事一覧へのリンク|

Discussion

齊藤敦志齊藤敦志

でも、もう安心してください!C++17でもうムーブコンストラクタがなくても戻り値が必ずムーブされるようになりました。

コピーもムーブもされません。 直接初期化 (direct initialization) 相当の挙動になります。

スペース・ソルバ株式会社スペース・ソルバ株式会社

コメントありがとうございます。
元の文章は「コピーされない」に注目したもので「ムーブであると限定したいわけではない」というものでした。
ご指摘通りのムーブへの限定は誤解を招くものとなっていましたので、表現を訂正しました。