92

C++11 を初めて使用しようとしていunique_ptrます。私のプロジェクト内のポリモーフィックな生のポインターを置き換えています。これは、1 つのクラスによって所有されていますが、非常に頻繁に渡されます。

以前は次のような機能がありました。

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

しかし、すぐに次のように切り替えることができないことに気付きました。

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

呼び出し元は関数へのポインターの所有権を処理する必要があるため、私はしたくありません。それで、私の問題に対する最良の解決策は何ですか?

次のように、ポインターを参照として渡すことを考えました。

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

_ptrしかし、私はそうすることに非常に不快に感じます.1つ目は、すでに入力されているものを参照として渡すことは本能的ではないように思われるためです。第二に、関数のシグネチャがさらに大きくなるためです。第 3 に、生成されたコードでは、変数に到達するために 2 つの連続したポインター間接参照が必要になるためです。

4

4 に答える 4

106

関数で pointee を使用する場合は、参照を渡します。関数をある種のスマート ポインターでのみ動作するように関連付ける理由はありません。

bool func(BaseClass& base, int other_arg);

そして、呼び出しサイトで次を使用しますoperator*

func(*some_unique_ptr, 42);

または、base引数を null にすることが許可されている場合は、署名をそのままにして、get()メンバー関数を使用します。

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);
于 2012-06-30T20:07:14.350 に答える
40

を使用する利点は(明示的にorstd::unique_ptr<T>を呼び出すことを覚えておく必要がないことを除けば)、ポインターが(ベース) オブジェクトの有効なインスタンスを指している、または指していることが保証されることです。あなたの質問に答えた後、私はこれに戻りますが、最初のメッセージは、動的に割り当てられたオブジェクトの有効期間を管理するためにスマート ポインターを使用することですdeletedelete[]nullptr

さて、あなたの問題は、実際にはこれを古いコードでどのように使用するかです

私の提案は、所有権を譲渡または共有したくない場合は、常にオブジェクトへの参照を渡す必要があるということです。次のように関数を宣言します (const必要に応じて、修飾子の有無にかかわらず)。

bool func(BaseClass& ref, int other_arg) { ... }

次に、呼び出し元は、ケースstd::shared_ptr<BaseClass> ptrを処理するか、結果を計算するように要求します。nullptrbool func(...)

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

これは、すべての呼び出し元が参照が有効であること、および関数本体の実行中ずっと有効であり続けることを約束する必要があることを意味します。


これが、生のポインターやスマート ポインターへの参照を渡すべきではないと私が強く信じている理由です。

生ポインタは単なるメモリアドレスです。(少なくとも) 4 つの意味のいずれかを持つことができます。

  1. 目的のオブジェクトが配置されているメモリ ブロックのアドレス。(良い)
  2. あなたが確信できるアドレス 0x0 は逆参照可能ではなく、「何もない」または「オブジェクトがない」というセマンティクスを持つ可能性があります。(悪い)
  3. プロセスのアドレス可能な空間の外にあるメモリ ブロックのアドレス (逆参照すると、プログラムがクラッシュする可能性があります)。(醜い)
  4. 逆参照できるが、期待するものを含まないメモリ ブロックのアドレス。ポインターが誤って変更され、別の書き込み可能なアドレス (プロセス内のまったく別の変数) を指している可能性があります。このメモリ位置に書き込むと、実行中に多くの楽しみが生じることがあります。これは、書き込みが許可されている限り、OS は文句を言わないためです。(ゾインクス!

スマート ポインターを正しく使用すると、通常はコンパイル時に検出できず、実行時にプログラムがクラッシュしたり予期しないことを行ったりするときにのみ発生する、かなり恐ろしいケース 3 と 4 が軽減されます。

引数としてスマート ポインターを渡すことには 2 つの欠点があります。コピーを作成せずにポイントさconstれたオブジェクトの-ness を変更することはできません(これは のオーバーヘッドを追加し、 では不可能です) 。shared_ptrunique_ptrnullptr

設計の観点から、2 番目のケースを (悪い)とマークしました。これは、責任に関するより微妙な議論です。

関数がパラメーターとして a を受け取るときの意味を想像してみてnullptrください。最初に、それをどうするかを決定する必要があります。不足しているオブジェクトの代わりに「魔法の」値を使用しますか? 動作を完全に変更し、何か他のものを計算します (オブジェクトを必要としません)? パニックして例外をスローしますか? さらに、関数が 2 つ、または 3 つ、あるいはそれ以上の引数を生のポインターで受け取るとどうなりますか? それらのそれぞれをチェックし、それに応じて動作を適応させる必要があります。これにより、本当の理由もなく、入力検証の上にまったく新しいレベルが追加されます。

発信者は、これらの決定を行うのに十分なコンテキスト情報を持っている必要があります。つまり、悪いことは、知れば知るほど怖くなくなります。一方、関数は、それが指しているメモリが意図したとおりに安全に動作するという呼び出し元の約束を取る必要があります。(参照は依然としてメモリアドレスですが、概念的には有効性の約束を表しています。)

于 2014-06-18T15:37:33.303 に答える
29

Martinho に同意しますが、参照渡しの所有権のセマンティクスを指摘することが重要だと思います。正しい解決策は、ここで単純な参照渡しを使用することだと思います。

bool func(BaseClass& base, int other_arg);

C++ で一般的に受け入れられている参照渡しの意味は、関数の呼び出し元が関数に「ここで、このオブジェクトを借用して使用し、(const でない場合) 変更することができますが、そのオブジェクトに対してのみ」と伝えるようなものです。関数本体の期間。」unique_ptrこれは、オブジェクトが単に短期間借りられているだけであり、実際の所有権の譲渡が行われていないため、所有権の規則に抵触することは決してありません (誰かに車を貸す場合、所有権に署名しますか?彼に?)。

したがって、参照 (または生のポインター) を から取り出すのは (設計上、コーディングの慣行などで) 悪いように思えるかもしれませんが、unique_ptr実際にはそうではありません。unique_ptr。_ そしてもちろん、他の素晴らしい利点もあります。たとえば、きれいな構文、 a が所有するオブジェクトのみに制限がないなどunique_ptrです。

于 2012-07-01T00:41:58.897 に答える
0

個人的には、ポインター/スマート ポインターから参照を取得することは避けています。ポインタが の場合はどうなるnullptrでしょうか。署名を次のように変更した場合:

bool func(BaseClass& base, int other_arg);

null ポインターの逆参照からコードを保護する必要がある場合があります。

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

クラスがポインターの唯一の所有者である場合、Martinho の代替案の 2 番目がより合理的に見えます。

func(the_unique_ptr.get(), 10);

または、 を使用することもできますstd::shared_ptr。ただし、 を担当するエンティティが 1 つだけの場合deletestd::shared_ptrオーバーヘッドは報われません。

于 2017-03-10T22:23:55.063 に答える