C++で関数引数をconst参照にするか値渡しにするか

C++で関数引数を書くとき、const T& にするか値渡しにするか迷うことがあります。

最初は、関数がその値をどう使うかで分けると整理しやすいです。

読むだけならconst参照

大きめのオブジェクトを読むだけなら、const T& がよく使われます。

#include <string>

void printName(const std::string& name) {
    // nameを読むだけ
}

コピーを避けつつ、関数内で変更しないことを表せます。

小さい型は値渡しでよい

intdouble のような小さい型は、値渡しで十分です。

int add(int a, int b) {
    return a + b;
}

無理に const int& にしても読みづらくなるだけです。

int add(const int& a, const int& b); // これはやりすぎ

関数内で保持するなら値渡しも候補

引数をメンバに保存する場合は、値渡しして std::move する形もあります。

class User {
public:
    explicit User(std::string name)
        : name_(std::move(name)) {}

private:
    std::string name_;
};

呼び出し側が右辺値を渡した場合はムーブしやすく、左辺値を渡した場合はコピーされます。

std::move の注意点は C++でstd::moveを使うときに気をつけること に分けています。

変更したいなら非const参照

関数内で引数を書き換えるなら、非const参照を使います。

void normalize(std::string& name) {
    // nameを書き換える
}

ただし、呼び出し側から見ると値が変わるので、使いどころは絞ります。

戻り値で返せるなら、戻り値にした方が読みやすいこともあります。

まとめ

関数引数は、用途ごとに分けると判断しやすいです。

  • 小さい型は値渡し
  • 大きい型を読むだけなら const T&
  • 関数内で保持するなら値渡しして std::move
  • 書き換えるなら T&
  • 所有権を渡すならスマートポインタやムーブを考える

全部を const T& にするのではなく、関数が何をするかを引数の型に出すと読みやすくなります。

関連記事