参照とは
参照(reference)とは、データそのものではなく、そのデータが格納されている場所を示す情報を持つ小さなオブジェクトのことです。これは、現実世界の住所に例えることができます。住所は、その場所にある家そのものではなく、家へのアクセスを可能にするための情報です。同様に、プログラムにおける参照は、実際のデータが格納されているメモリ上の場所を指し示します。参照が指し示す値を取り出す操作は「デリファレンス」と呼ばれます。
参照の例:住所
例えば、家の住所は、その家自体の情報(色や形など)は持っていませんが、住所を知っていれば、その家にたどり着き、家の情報を知ることができます。この住所が参照に相当します。
参照の活用:引越し
さらに、引越しの際に、新しい家の住所を古い家に残していくという例を考えてみましょう。この場合、最初の家から置いてある住所をたどっていくことで、最終的に現在の家にたどり着くことができます。これは、参照を使い線形リストを作成するようなイメージです。
参照の利点
参照の大きな利点は、データそのものを移動させることなく、データにアクセスできることです。例えば、住所録を五十音順に並び替える際、家そのものを物理的に移動させる必要はありません。住所を並び替えるだけで、目的を達成できます。これは、実際のデータを操作するよりも効率的です。
日常生活における参照の例としては、電話番号、メールアドレス、URLなどが挙げられます。これらの情報は、それぞれ別の場所にあるリソースを指し示し、それらへのアクセスを可能にします。
参照の利点
参照を使用することによって、データの格納場所、格納方法、コード内での引き渡しなどにおいて、柔軟性が増します。また、複数のコードから同じデータを共有することができ、効率的なデータ管理が可能になります。
ポインタとスマートポインタ
ポインタは、メモリ上のアドレスを格納した最も基本的な参照です。ただし、ポインタは直接メモリを操作するため、扱いを間違えるとエラーの原因になる可能性があります。スマートポインタは、ポインタのように振る舞いますが、特定のメソッドを経由しないとアクセスできないように制限を加えたものです。
ファイルハンドル
ファイルハンドルは、ファイルの内容を抽象化する参照です。ファイルへのロックや、ファイル内の特定の位置を指し示す役割を担います。
形式表現
より一般的に表現すると、参照は、あるデータの一意な検索を可能にする別のデータとみなすことができます。
データベースの
主キーや連想
配列のキーなども参照の一種とみなすことができます。
参照の形式的な定義は、データの集合Dについて、DからD∪{null}への一意に定まる関数と考えることができます。ここで、nullは意味のあるものを指さないデータです。また、参照は「到達可能性グラフ」と呼ばれる有向グラフで表現することもできます。このグラフでは、データが頂点として表され、あるデータから別のデータへのエッジは参照を表します。このグラフ構造は、
ガベージコレクションなどにおいて、到達不能なオブジェクトを識別するのに役立ちます。
外部ストレージと内部ストレージ
多くの
データ構造では、複雑なオブジェクトが小さなオブジェクトの集合から構成されています。これらのオブジェクトの格納方法には、内部ストレージと外部ストレージの2種類があります。
内部ストレージ
内部ストレージでは、小さなオブジェクトの内容は大きなオブジェクトの内部に格納されます。この方法では、参照のための領域や動的メモリアロケーションのオーバーヘッドが少なく、高速な処理が可能です。また、同種の大きなオブジェクトをメモリ上に連続して配置することで、
参照の局所性を高める効果があります。
外部ストレージ
一方、外部ストレージでは、小さなオブジェクトは独立した場所に格納され、大きなオブジェクトはそれらへの参照のみを保持します。この方法では、以下のような状況で有利です。
データ構造が再帰的な場合、内部ストレージは不可能
大きなオブジェクトが制限された領域に格納される場合、オーバーランを防ぐために物理的なサイズを小さくする必要がある
小さなオブジェクトのサイズが可変の場合、大きなオブジェクトを可変サイズにする必要がなく、効率的
仕様変更などに柔軟に対応できる
Javaでは、
プリミティブ型は内部ストレージであり、オブジェクト(クラス型)や
配列は外部ストレージです。
プログラミング言語における参照
アセンブリ言語では、参照は
メモリアドレスや
配列のインデックスで表現されます。ただし、
メモリアドレスだけでは、それが指すデータの内容や構造がわからないため、注意が必要です。
LISP
LISP言語のconsセルは、最初期の不透明参照の1つです。consセルは、他の2つのLISPオブジェクトへの参照を持ち、線形リストや
二分木を構築するために使用されます。
FORTRAN
初期のFORTRAN言語には明示的な参照はありませんが、参照渡しによって暗黙的に参照を使用しています。
C言語では、ポインタが原始的な参照の形態として導入されました。ポインタは、静的な
データ型を持つ点で
アセンブリ言語のアドレス表現よりも安全ですが、
型変換によって不正なポインタが生成される可能性があり、注意が必要です。
C++では、ポインタに加えて、「参照」という型が導入されました。
C++は、型安全性を強化しようと試みていますが、Cとの互換性を維持するために、これらの安全機構を出し抜くことが可能です。
ガベージコレクションをサポートする
高水準言語では、不透明な参照を採用しています。これらの参照は
C言語のポインタよりも安全であり、生のアドレス値への変換やアドレス値からの参照の生成が制限されています。これらの参照は、実際にはポインタへのポインタとして実装されることが多く、ガベージコレクタだけが中間のポインタにアクセスできます。
FORTRANの参照
FORTRANでは、参照はオブジェクトの別名として扱われることが多く、参照をデリファレンスするという概念は存在しません。
Javaでは、クラス型、インタフェース型、型変数、
配列型が参照型として扱われます。
C#の参照型
C#では、クラス、インターフェイス、
配列、デリゲートが参照型として扱われます。
構造体や列挙型は値型です。
関数型言語
関数型言語における参照は、通常、副作用を避けるために特定の制限が加えられています。
まとめ
参照は、プログラムにおいてデータを効率的に管理するための重要な概念です。様々なプログラミング言語で異なる形で実装されていますが、いずれもデータへのアクセスを間接的に行うという点で共通しています。参照を正しく理解し活用することで、より効率的で安全なプログラムを作成することができます。
関連項目
* 弱い参照