web-dev-qa-db-ja.com

std :: unique_ptrを渡す方法は?

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として既に入力されたものを参照として渡すのは本能的ではないように思えるので、そうすることは非常に不快です。第二に、関数シグネチャがさらに大きくなるためです。第三に、生成されたコードでは、変数に到達するには2つの連続したポインター間接指定が必要になるためです。

67
lvella

関数で指示先を使用する場合は、参照先を渡します。何らかのスマートポインターでのみ機能するように関数を結び付ける理由はありません。

_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);
_
82

_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 {
  /* the object was, for some reason, either not created or destroyed */
}
_

つまり、呼び出し元は、参照が有効であり、関数本体の実行中も有効であり続けることをpromiseする必要があります。


ここに、生のポインタまたはスマートポインタへの参照をnot渡す必要があると強く信じる理由があります。

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

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

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

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

デザインの観点から、2番目のケースを(the bad)とマークしました。これは、責任に関するより微妙な議論です。

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

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

25
Victor Savu

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

bool func(BaseClass& base, int other_arg);

C++での参照渡しの一般的に受け入れられている意味は、関数の呼び出し元が関数に「ここで、このオブジェクトを借用し、使用し、(constではない場合)変更することができますが、関数本体の期間。」これは、unique_ptrの所有権規則と矛盾することは決してありません。なぜなら、オブジェクトは短期間借用されているだけで、実際の所有権移転は発生しないからです(車を誰かに貸した場合、彼にタイトルを譲りますか?)。

そのため、unique_ptrから参照(または生のポインター)を引き出すことは(設計上、コーディング慣行など)悪く見えるかもしれませんが、実際には、それが完全に一致しているからではありませんunique_ptrによって設定された所有権ルール。そして、もちろん、きれいな構文、unique_ptrが所有するオブジェクトのみに制限がないなど、他の素晴らしい利点もあります。

21
Mikael Persson

個人的には、ポインター/スマートポインターから参照をプルすることは避けています。ポインターが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を使用できます。ただし、deleteを担当するエンティティが1つしかない場合、std::shared_ptrオーバーヘッドは報われません。

0