使用する利点はstd::unique_ptr<T>
(呼び出すことdelete
やdelete[]
明示的に呼び出す必要がないことを除けば)、ポインターがnullptr
(ベース)オブジェクトの有効なインスタンスであるか、それを指していることを保証することです。私はあなたの質問に答える後、私はこれに戻ってくるだろうが、最初のメッセージがあるDOが動的に割り当てられたオブジェクトの寿命を管理するスマートポインタを使用します。
さて、あなたの問題は実際にあなたの古いコードでこれをどのように使うかです。
私の提案は、所有権を譲渡または共有したくない場合は、常にオブジェクトへの参照を渡す必要があるということです。このように関数を宣言します(const
必要に応じて、修飾子の有無にかかわらず):
bool func(BaseClass& ref, int other_arg) { ... }
次に、を持っている呼び出し元std::shared_ptr<BaseClass> ptr
がnullptr
ケースを処理するかbool func(...)
、結果を計算するように要求します。
if (ptr) {
result = func(*ptr, some_int);
} else {
}
つまり、呼び出し元は、参照が有効であり、関数本体の実行中も引き続き有効であることを約束する必要があります。
これが、生のポインターやスマートポインターへの参照を渡すべきではないと私が強く信じている理由です。
生のポインタは単なるメモリアドレスです。(少なくとも)4つの意味のいずれかを持つことができます:
- 目的のオブジェクトが配置されているメモリブロックのアドレス。(良い)
- 確実にできるアドレス0x0は参照解除できず、「nothing」または「noobject」のセマンティクスを持っている可能性があります。(悪い)
- プロセスのアドレス可能なスペースの外にあるメモリブロックのアドレス(逆参照すると、プログラムがクラッシュする可能性があります)。(醜い)
- 逆参照できるが、期待するものが含まれていないメモリブロックのアドレス。ポインタが誤って変更され、(プロセス内の完全に他の変数の)別の書き込み可能なアドレスを指している可能性があります。このメモリ位置に書き込むと、実行中に多くの楽しみが生じることがあります。これは、そこに書き込むことが許可されている限り、OSが文句を言わないためです。(Zoinks!)
スマートポインタを正しく使用すると、通常はコンパイル時に検出できず、通常は実行時にプログラムがクラッシュしたり予期しないことを行ったりした場合にのみ発生する、かなり恐ろしいケース3と4が軽減されます。
スマートポインタを引数として渡すことには2つの欠点があります。コピーを作成せずにポイントされたオブジェクトのconst
-nessを変更することはできず(オーバーヘッドが追加され、では不可能です)、2番目の()の意味が残ります。shared_ptr
unique_ptr
nullptr
設計の観点から、2番目のケースを(悪い)としてマークしました。これは責任についてのより微妙な議論です。
関数nullptr
がパラメータとしてaを受け取ったときの意味を想像してみてください。まず、それをどうするかを決定する必要があります。欠落しているオブジェクトの代わりに「魔法の」値を使用しますか?動作を完全に変更し、他の何かを計算します(オブジェクトを必要としません)?パニックになって例外をスローしますか?さらに、関数が生のポインターによって2つ、3つ、またはそれ以上の引数を取るとどうなりますか?それらのそれぞれをチェックし、それに応じてその動作を適応させる必要があります。これにより、実際の理由なしに、入力検証にまったく新しいレベルが追加されます。
発信者は、これらの決定を下すのに十分なコンテキスト情報を持っている必要があります。言い換えると、悪いことは、あなたが知っているほど怖くないということです。一方、関数は、ポイントされたメモリが意図したとおりに安全に動作するという呼び出し元の約束を受け入れる必要があります。(参照は依然としてメモリアドレスですが、概念的には有効性の約束を表しています。)
std::unique_ptr
はstd::vector<std::unique_ptr>
議論のためにを取り除くカムですか?