読者です 読者をやめる 読者になる 読者になる

C++ と const 参照

数年前,まだ私が C++ でゲームを書いていたころ,深すぎる関数のネストは「一旦変数に入れ」て「読みやすくする」というコーディングスタイルにだいたい落ち着いていた.もちろん,「これは一旦別名を付けるだけで,変数に再代入する気はないよ」ということで const も付ける.ただあまり大きなデータのコピーは嫌だよねということで,戻り値が std::string や構造体の関数呼び出しを「一旦変数に入れる」ときは const 参照を好んで使っていた.

A(B(C(a), b, D(E(c), d, e)));

「む,なんて読みにくいコード.ばらせよ」

const int target_id = C(a);
const MessageBody& message_body = D(E(c), d, e);
const Message& msg = B(target_id, b, message_body);
A(msg);

とかなんとか.
んで,次のような振る舞いをすっかり忘れているわけだ.

#include <iostream>
#include <vector>
void foo(const std::vector<int>& src, std::vector<int>& dest)
{
    const int& cached_ref = src[0];
    const int cached_copy = src[0];
    std::cout << "cached_ref:  " << cached_ref << std::endl;
    std::cout << "cached_copy: " << cached_copy << std::endl;
    dest[0] = 2;
    std::cout << "cached_ref:  " << cached_ref << std::endl;
    std::cout << "cached_copy: " << cached_copy << std::endl;
}
int main()
{
    std::vector<int> v;
    v.push_back(1);
    foo(v, v);
    return 0;
}

/* result
cached_ref:  1
cached_copy: 1
cached_ref:  2
cached_copy: 1
*/

const 参照は,参照先の内容まで immutable だとは言っていない.たとえるならファイルをリードオンリーでメモリマップしたようなものだ.誰かがリードライトで同じファイルを開いて書き込めば,当然中身は変わっている.const 参照の意味は「この変数を通しては参照先を書き換えませんよ」であって,「この変数の中身は不変ですよ」ではない.
少しひねってみる.

class Data
{
    std::string m_string;
public:
    void Deserialize(const std::string& str)
    {
        m_string = str;
    }
    const std::string& SerializeAsString() const
    {
        return m_string;
    }
};

void bar(const Data& src, Data& dest)
{
    const std::string& serialized = src.SerializeAsString();
    std::cout << serialized << std::endl;

    // 長い処理

    dest.Deserialize( "hauhau" );

    // 長い処理

    std::cout << serialized << std::endl;
}

関数内で「内容が不変なキャッシュ」のように使われている const 参照があったとき,考えるべきことは色々ある.確かにその参照は,初期化時と同じオブジェクトを指し続けてはいるだろうが,その中身まで同じとは限らない.

  • 参照の初期化に使われている関数の戻り値は値のコピーなのか const 参照なのか.戻り値が const 参照であれば警戒レベルを上げる必要がある.が,しばしばそれはソースコードの断片から判断が付かない.
  • 関数の戻り値が const 参照であった場合,コンパイラの型検証は参照内容の不変性についてなにも教えてくれない.このとき参照内容の不変性はプログラムの設計次第.そしてこれもソースコードの断片から判断できるか分からない.