web-dev-qa-db-ja.com

C ++ Gofデザインパターンは、newまたはshared_ptrに大きく依存します

コードの設計とC++での再利用のベストプラクティスを学ぼうとしているので、よく知られているGoF Design Patterns Elements of Reusable Object-Oriented Software。

ほとんどすべてのデザインパターンが動的に割り当てられたオブジェクトを使用していることに気づきました。メモリリークを防止するために、私はshared_ptrこれらすべてのクラス。基本的に、shared_ptrは通常、抽象クラスインターフェイスを指し、オブジェクトが相互に作用する方法です。

C++のshared_ptrsに関するディスカッションを調べると、C++コードのどこでもshared_ptrsを使用している場合、おそらく間違っていると思われます。これらの人々は彼らが話していることを知っていますか、それとも私はすべてのオブジェクトに動的ヒープ割り当てを使用することに固執すべきですか?

これは私がこのデザインの実践を避けるために言っているコメントを見つけた1つの例です: https://stackoverflow.com/questions/25357499/is-it-possible-in-any-way-using-c-preprocessor-etc -to-replace-shared-ptrt

共通インターフェースの例

std::shared_ptr<Inferface> item1(new ConcreteClassA());
std::shared_ptr<Inferface> item2(new ConcreteClassB()); 
item1->Action();
item2->Action();

共通インターフェースとヒープ割り当てを回避する例

void Action(ConcreteClassA item){
     item.Action();
}
void Action(ConcreteClassB item){
     item.Action();
}

ConcreteClassA item1;
ConcreteClassB item2;
Action(item1);
Action(item2);

最初の例は、新しいクラスごとに新しい関数を記述する必要がないため、コードの再利用に優れていますが、これにはshared_ptrsとヒープ割り当てが含まれます。これは、デザインパターンが役立つ理由を非常に単純化した例であり、明らかにこれよりもはるかに複雑になります。

私は、C++を使用したソフトウェア設計の一般的な手法と見なされているアプローチについて、混乱しているところです。 GoFブックのWYSIWYGテキストエディタの例のように、私が数年にわたって維持する必要のあるアプリケーションソフトウェアを作成しているとしましょう。他の人が言うとき、オブジェクトを管理するためにshared_ptrを広範囲に使用するべきではないのですか?それは、他のスマートポインター/生ポインターを使用する必要があることを意味しますか、それとも動的ヒープ割り当てを完全に回避するための一般的なC++プラクティスですか?

私には、OOPガベージコレクションが自動的に行われ、インターフェイスが言語の一部であるJavaのような言語の方が意味があります。

業界は、C++でアプリケーションを開発する際に動的割り当てを使用して設計パターンに従うことを実践していますか?もしそうなら、スマートポインタは正しい方法ですか?

3
Christian Gabor

GoFデザインパターンは、ポリモーフィズムを使用してデザインを拡張可能に保つための巧妙な方法です。たとえば、戦略パターンを使用すると、戦略を使用するコードを再コンパイルすることなく、さまざまな戦略の実装を提供できます。

オブジェクト指向の手法(ポリモーフィズム/動的ディスパッチ)は、オブジェクトがある場合、それが実際の型であることを知る必要がないことを意味します。 InterfaceであるかConcreteClassAであるかではなく、ConcreteClassBを知っていれば十分です。オブジェクトのタイプが完全にわかっている場合は、動的ディスパッチを使用していません。オブジェクトが値によってローカル変数に格納されるとき。オーバーロードされた関数を使用すると、コンパイル時に静的ディスパッチによって解決され、動的ディスパッチの拡張性プロパティはありません。詳細については、私の記事を読んでください 動的または静的ディスパッチ

オブジェクトのインターフェースのみを知っていて、その動的な型を知らない場合は、ポインタを介してオブジェクトを保持する必要があります。これは、生のポインタ、参照、またはスマートポインタです。これらの異なるポインタタイプは、異なる所有権セマンティクスを意味します。

  • 生のポインタは所有権がないことを意味するため、回避する必要があります。これを削除する必要がありますか、それともオブジェクトを借用しただけですか?はっきりしない。生のポインタは、メモリリークとセグメンテーションフォールトのレシピです。生のポインタを使用して例外セーフなコードを記述することは困難です。
  • 参照とは、オブジェクトが一時的に借用され、他のオブジェクトが所有していることを意味します。便利:参照をnullポインターにすることはできず、明示的に逆参照する必要もありません。
  • A std::unique_ptrは、ポイントされたオブジェクトの所有権を譲渡します。スマートポインターが自動的に破棄されると、ポイントされたオブジェクトも削除されます(→RAII)。参照または共有ポインターがより適切である場合を除き、これがデフォルトのポインター型である必要があります。
  • A std::shared_ptrは共有所有権を示します。指し示されたオブジェクトは自動的に参照カウントされます。そのオブジェクトを参照する最後の共有ポインタが破棄されると、ポイントされたオブジェクトも削除されます。参照カウントは、実行時のオーバーヘッドを意味します。

したがって、共有ポインターを回避するためのアドバイスは正しいです。多くのオブジェクトグラフは明確な所有権を持ち、参照カウントのようなガベージコレクションのアプローチを必要としません。それでも必要な場合は、オプションがまだあります。共有ポインタは、Javaスタイルのガベージコレクションとはかなり異なることに注意してください。これは、参照ポインタを(たとえば、弱いポインタを使用して)回避する必要があるためです。

GoFパターンはオブジェクト指向の手法のみを考慮しますが、C++はそれ以上のものです。特に、テンプレートは時々同様の問題に対処できます。ただし、テンプレートとOOPは根本的に異なります。重要なことに、テンプレートを変更すると、すべての依存コードを再コンパイルする必要がありますが、OOPの手法ではコンポーネントを分離できます。

9
amon

より良い「ルール」は「ネイキッドノー」です。該当する場合は、適切なスマートポインターを使用する必要があります。

std :: unique_ptr

弱いポインタ以外は共有されない一意の割り当てに使用されます。一意のポインタには単一の所有者がいます

std :: shared_ptr

所有者のいない共有割り当てに使用されます

std :: weak_ptr

非所有権参照に使用されます


Unique_ptrがweak_ptr参照を返すことで、デザインパターンの多くの用途を実装できます。共有ポインタは、所有者がいないか、作成者よりも長く続く割り当てにのみ使用してください。

3
esoterik