【C++再履修】参照型のメンバ変数の初期化

参照型の復習

int a1 = 10;
int a2 = 20;

int &b = a1;
// b はa1への参照
// 「別名」と考えるとよいかも
printf("%d %d %d\n", a1, a2, b);  // 10 20 10

b = a2;
// これは、
// bの参照先=a1の中身を書き換えることになる。
// 参照先をa2に変えるわけではない。
// 参照先は初期化時にのみ設定できてその後変更することはできない。
printf("%d %d %d\n", a1, a2, b);  // 20 20 20

// つまり、
int &c;
// これはエラーになる。必ず宣言時に初期化しないといけない。

参照型のメンバ変数の初期化

メンバ変数の場合は「コンストラクタのメンバ初期化子リスト」で初期化する。
コンストラクタ内で代入するのはコンパイルエラーになる。(これは初期化ではない)

class C {
public:
  C(int &v1) : m_a1(v1) {}
//C(int &v1) { m_a1 = v1; }  // こちらはエラー
private:
  int &m_a1;
}


ちなみにコンストラクタの引数の型を誤って int v1 としてしまうと、特にエラーはないが問題が起きるので注意が必要。

class C {
public:
  C(int v1) : m_a1(v1) { printf("%d\n", m_a1); }
  void PrintA1() { printf("%d\n", m_a1); }
private:
  int &m_a1;
}

// ---
int a1 = 10;
C test(a1);  // ここでは正しく10と出る
test.PrintA1(); // ここでは不定の壊れた数値が出る(処理系による)

コンストラクタで受け取った v1 は参照ではなくスタックにコピーされた実体なので、メンバ変数 m_a1 はスタック領域のどこかを指すことになり、コンストラクタを抜けた時点でその領域は破棄され、不正に場所を指してしまう。


実際のところ上記のようなint型でコードを見てもピンとこないかも。
このコードを書こうと思ったきっかけは、以下のように処理の入力となるvectorをconstの参照で受け取ってクラス内に保持しておきたいなぁと思ったのでした。

class C {
public:
  C(const std::vector<int> &v1) : m_a1(v1) {}
private:
  const std::vector<int> &m_a1;
}


…ところでメンバ変数に「m_」とかってもう今どきあまりつけないのでしょうか。