C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化
C++の参照の宣言方法、振る舞い、一時オブジェクトによる初期化について整理したいと思います。
参照の宣言
C++の参照は、以下のように宣言します。
TypeName &AnotherName = ObjectName;
「&」演算子は、式中で変数の前に置くとそのレコードのアドレス(いわゆるポインタ)を返しますが、初期化式で使うと参照変数またはエイリアスの宣言とみなされます。エイリアスとは別名という意味です。C++の参照は「別名」なので以下のような初期化なしでの参照変数の宣言はできません。
ClassName &AnotherName; //エラー AnotherName = ObjectName;
参照の振る舞い
参照変数は、初期化後は参照しているオブジェクトと「同様」に振る舞うので、
AnotherName = TheOtherObjectName;
上記の式はAnotherNameが参照しているObjectNameへのTheOtherObjectNameのコピーを行います。TheOtherObjectNameをその参照変数に置き換えても同じです。したがって、C++では一度宣言した参照変数の参照先を変更する方法はありません。別の言い方をすると参照変数の参照のみを入れ替えるということはできません。そのため、C++では一度宣言した参照変数の参照先は不変です。変数にオブジェクトの参照が入り、変数の参照先を入れ替えることができるJavaやJavaScriptに慣れた方は初めは違和感を覚えるかもしれません。
一時オブジェクトによる初期化
下記の一時オブジェクトによる参照変数の初期化はエラーになります。
int &VariableName = 123; //エラー
参照型は参照のための領域を確保するだけなので、参照すべき123はどこにも確保されず初期化後に消えてしまいます。
一方で例外的に、
const int &VariableName = 123;
は成立します。通常、初期化後に削除される一時オブジェクトが、const宣言された参照を初期化する場合には例外的に存続するためです。ちなみに、どうしてそのような例外的な処理をするのかですが、私は一応以下のように理解しています。
const宣言付きの一時オブジェクトによる参照の初期化
const宣言なしの場合にコンパイルエラーになる理由
const宣言なしの場合には、参照を通して参照元を変更することができます。つまり、参照を通してなされた変更が参照元に反映されることになります。もしも、右辺の参照元がなくなっていたり、参照先が右辺のオブジェクトとは異なるオブジェクトになってしまった場合には、その変更は参照元に正しく反映されないことになります。このようなことが起これば参照は不安定な機構になりますし、そもそも参照とは言えなくなります。そのためコンパイルエラーになります。
ここで、右辺の参照元がなくなっている場合というのは、上述もした
int &VariableName = 123; //エラー
のような例です。
参照先が右辺のオブジェクトとは異なるオブジェクトになってしまった場合というのは、参照先となるはずの右辺のオブジェクトから型変換されて生じた一時オブジェクトにより、参照が初期化される場合です。作成された一時オブジェクトを参照してしまっては、参照をしているつもりのオブジェクトへの変更が反映されず混乱が生じます。そのためコンパイルエラーになります。
ちなみに、C++には参照変数の初期化において左辺の参照変数の型が、右辺の参照元のオブジェクトの型をアップキャストしてできる場合には初期化が成立し、その右辺のオブジェクトを直接参照し続けるという仕様があります。一方で、アップキャストができない場合には、型変換された一時オブジェクトを参照するということはせず、前述のとおりコンパイルエラーになります。あくまでも右辺値を参照元とできるかどうかで成否を分け、参照における混乱を生じさせないようになっているようです。
以上のことから、const宣言なしの場合には一時オブジェクトによる参照変数の初期化はできません。
const宣言ありの場合に成立する理由
一方でconst宣言ありの場合は一時オブジェクトによる参照変数の初期化ができます。参照変数の初期化にconst宣言が付けられると、参照を通して参照元を変更することができなくなります。そうすると、たとえ一時オブジェクトを参照元としても、const宣言なしの場合に問題になった参照を通してなされた変更が参照元に反映されないという問題が、そもそも変更を加えることができないので問題ではなくなります。このため、const宣言ありの場合は一時オブジェクトによる参照変数の初期化が成立するのです。
さらに、一時オブジェクトを参照できることで、アップキャストできる場合だけではなく、型変換や式の評価の結果を柔軟にconst宣言の参照元として扱えるようになります。
ちなみに、const宣言を付けて参照変数を初期化した場合に、参照元が右辺のオブジェクトかあるいは型変換や式の評価によって生じた一時オブジェクトかによって生じる唯一の違いは、右辺のオブジェクトを参照を通さずに変更した場合に、その変更がconstである参照にも反映されるかされないかということです。右辺のオブジェクトへの直接的な参照であれば、変更は反映されます。それはC++の参照のconst宣言の効果が、「参照を通しての変更ができない」であって「参照元を変更できない」ではないからです。一方、型変換や式の評価によって生じた一時オブジェクトを参照している場合には、別のオブジェクトである右辺のオブジェクトへの変更は、参照には反映されないことになります。
参照のconst宣言の効果についてはC++のconst宣言の対象、効果、効果範囲、その他の注意点をご覧ください。
参考サイト
C++編(言語解説) 第15章 参照, Programing Place, 2012/1/25
一時オブジェクト,ソフトウェア インフォーメイション センター IBM
参照の初期化 (C++ のみ),ソフトウェア インフォーメイション センター IBM