弱参照の使い所
弱参照は強参照あるいはガベージコレクションの文脈で名前が出ることが多いので、強参照の代わりに使うという印象があります。例えば、強参照ではオブジェクトが延命されてしまうのでそれを避けるために弱参照を使う。あるいは、強参照の天敵である循環参照を回避するため部分的に強参照の代わりに弱参照を使うなどです。ですが、弱参照の一番の使い所はそこではないのです。
オブジェクトの削除を検知できる参照としての弱参照
弱参照の一番の使い所は、オブジェクトの削除を検知したい場合です。弱参照は参照先のオブジェクトが削除された場合に参照が無効化されるので、弱参照を使うことでオブジェクトがまだ生きているかどうかを知ることができます。C++での通常の参照(T&)やポインタではこれができません。通常の参照(T&)やポインタではオブジェクトが削除された場合、それを知ることができないので削除されたオブジェクトに不正にアクセスすることになってしまいます。これはダングリングポインタと呼ばれ、C/C++での危険な罠の一つになっています。
ダングリングポインタを回避する方法は弱参照だけではありませんが、弱参照を使った方法が手軽で確実かつコストパフォーマンスも良いと思います。
弱参照で「オブジェクトの削除を検知する」vs 強参照で「オブジェクトを延命させる」
弱参照を「オブジェクトの削除を検知する」ために利用できると述べましたが、安全性のため、つまりダングリングポインタを回避するためということであれば、強参照によって「オブジェクトを延命させる」という回避策もあります。では、「オブジェクトの削除を検知する」のと「オブジェクトを延命させる」のはどちらが良いのでしょうか。
弱参照で「オブジェクトの削除を検知する」メリット
私は基本的には、弱参照で「オブジェクトの削除を検知する」方を選ぶのが良いと思います。弱参照を選ぶ理由、あるいは強参照を選ばない理由は以下のとおりです。
- オブジェクトの所有権は明確にしたほうがよい
- 延命されたオブジェクトへの処理は意味がないことが多い
- 強参照はメモリによくない
オブジェクトの所有権は明確にしたほうがよい
強参照の仕組みは本質的には「オブジェクトの共有」です。複数の者がオブジェクトの共有を行うことになります。これはオブジェクトの所有権が曖昧になることでもあります。オブジェクトの所有権が曖昧だとオブジェクトの寿命も曖昧になり、オブジェクトがいつまで生存しているのか予想がつかなくなります。これはプログラムの不具合の原因となる可能性があります。
もちろん、オブジェクトの所有権を明確にすることよりも、所有権を曖昧にしてオブジェクトを確実に生存させることの方が重要な場合もあります。そのような場合は強参照を使ったほうがいいのですが、そういう状況は少ないのではないかと思います。
延命されたオブジェクトへの処理は意味がないことが多い
本来削除されているべきオブジェクトが強参照により延命させられていた場合、そのオブジェクトへの処理は基本的には意味がないものです*2。オブジェクトが延命されたことによりプログラムは落ちないかもしれませんが、意味のない処理を行うのは無駄ですしそこからバグが発生する可能性もあります。
オブジェクトが「延命させられている」のではなくの「意図して生存させている」のであれば問題ありませんが、先ほども言ったようにそのような状況は少ないと思います。
強参照はメモリによくない
強参照はメモリにとって良くないことが多いです。
- 意図せずメモリに残り続ける可能性がある
- 循環参照によりメモリリークが発生する可能性がある
- メモリの断片化を引き起こしやすい
意図せずメモリに残り続ける可能性がある
強参照で参照されているオブジェクトは、どこか1箇所からでも参照されている限り削除されません。そのため、意図せずオブジェクトが生存を続けてしまう可能性があります。当然、それは無駄にメモリを食うことになります。
弱参照の場合、オブジェクトの延命を行わないので、意図せずオブジェクトを生存させてしまうことによる無駄なメモリ消費はありません。
C++の強参照であるstd::shared_ptr
には、循環参照があるとオブジェクトが永遠に削除されないという問題があります。オブジェクトが削除されないということは永遠にメモリに残り続けることになるのでメモリリークとなり最悪の場合、メモリ不足でプログラムが落ちます。
弱参照の場合は、そもそもオブジェクトの延命が行われないので循環参照をしていても問題ありません。
メモリの断片化を引き起こしやすい
メモリは確保と解放をスタックのようにLIFO(後入れ先出し)にしないと断片化していきます。メモリの断片化はメモリが潤沢な環境ではたいした問題になりませんが、ゲーム機などメモリが少ない環境だとメモリの確保ができなくなり致命傷となってしまいます。
強参照によるオブジェクトの延命はメモリの解放の順番を変えてしまうため、容易にメモリの断片化を引き起こしてしまいます。
弱参照を使う場合はオブジェクトが延命されないので、強参照のようにオブジェクトが延命されることによるメモリの断片化は発生しません。ただし、弱参照でも参照カウントなどを管理するデータ領域は弱参照同士で共有するため、その管理データ領域が延命され、メモリの断片化が発生する可能性はあります。これを防ぐためには管理データ領域用のメモリ確保を工夫する必要があります。*3
強参照の使い所
弱参照のメリットが多いとはいえ、もちろん強参照にも使い所はあります。
- 参照先のオブジェクトの生存を保証する
- オブジェクトの所有権を扱う
参照先のオブジェクトの生存を保証する
強参照で参照されている間、オブジェクトは絶対に破棄されません*4。つまり、強参照で参照している間は確実にオブジェクトへアクセスできるわけです。これは強参照の大きなメリットです。弱参照では、参照している間にオブジェクトが削除される可能性を考慮してプログラミングを行う必要があります。
オブジェクトの所有権を扱う
強参照はオブジェクトの所有権を保持/複製(共有)/放棄/移動*5することができます。これらは弱参照ではできないことです。
まとめ
- 弱参照を使うことでオブジェクトの削除を検知できる
- 弱参照を使うことで不正な参照(ダングリングポインタ)を回避できる
- 強参照でも不正な参照は回避できるが、弱参照にはメリットが多い
- もちろん強参照を使うべき場面もある
(補足)std::weak_ptrの使い勝手の悪さについて
C++での弱参照といえばstd::weak_ptr
ですが、残念ながら「オブジェクトの削除を検知するための参照」としては使い勝手が悪いです。
std::shared_ptr
からしかstd::weak_ptr
を作ることができない*6
- つまり、
std::shared_ptr
管理下にないオブジェクトや自動変数からstd::weak_ptr
を作り出すことができない
std::weak_ptr
から直接参照先にアクセスできない(std::shared_ptr
を経由する必要がある)
このようにstd::weak_ptr
はstd::shared_ptr
に強く結びついており、手軽には扱えないようになっています。おそらく、std::weak_ptr
はオブジェクトの削除を検知するためではなく、std::shared_ptr
の弱点を補うために用意されたクラスなのでしょう。まぁ本来、弱参照はそういうものですし。
弱参照の仕組み自体はシンプルなので、「オブジェクトの削除を検知するための参照」が欲しい場合は自作するという手段もあります。(追記: 自作方法についての記事を書きました → 【C++】弱参照クラスを自作する)
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください