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