1

オブジェクトが所有していないbオブジェクトを内部的に参照して使用する場合、の死は病気になる可能性があります。ポイントを説明するための最小限の例を次に示します。aab

#include <iostream>

const int my_int = 5;

class A {
  private:
    int n_;
  public:
    int n() const { return n_; }
    A(int);
};

A::A(int n__) : n_(n__) {}

class B {
  private:
    const A *ap_;
  public:
    int m() const { return 1 + ap_->n(); }
    explicit B(const A *);
};

B::B(const A *const ap__) : ap_(ap__) {}

int main()
{
    std::cout << "Will put an unnamed A on the heap.\n";
    A *const p = new A(my_int);
    std::cout << "Have put an unnamed A on the heap.\n";
    std::cout << "p->n() == " << p->n() << "\n";
    B b(p);
    std::cout << "b. m() == " << b. m() << "\n";
    std::cout << "Will delete  the unnamed A from the heap.\n";
    delete p;
    std::cout << "Have deleted the unnamed A from the heap.\n";
    std::cout << "b. m() == " << b. m() << "\n"; // error
    return 0;
}

もちろん、ポインタではなくbのコピーを保持することでこれを修正できますが、コピーを保持しない方がよいと仮定します(大量のメモリを占有するなどの理由で)。単に既存のを参照することを好むと仮定します。静かに死ぬとき、決して気づかない。次に、を使用しようとすると、予測できない動作が発生します。ababaabba

私のコンピューターでは、例の出力は次のようになります。

Will put an unnamed A on the heap.
Have put an unnamed A on the heap.
p->n() == 5
b. m() == 6
Will delete  the unnamed A from the heap.
Have deleted the unnamed A from the heap.
b. m() == 1

ただし、コンピュータでは、結果はセグメンテーション違反または誰が何を知っているかである可能性があります。

私の例の問題は、例が間接的にのカプセル化を破り、の継続的な有効性がの継続的な存在に依存するbことを覚えておくようにプログラマーに任せていることにあるようです。プログラマーが忘れると、プログラムは壊れます。したがって、嫌がらせを受けたプログラマーは、タイプA自体がタイプBを気にしない場合でも、作業するときは常に覚えておく必要があります。ご存知のように、オブジェクト指向プログラマーは、彼らがそれを助けることができるならば、そのような雑学を心に留める必要がないことを好みます。baba

私はプログラミング中に時々より複雑な装いでこの問題に遭遇します。今日も会いました。どういうわけか、の適切なカプセル化を維持しb、プログラマーからコンパイラーに「存在bへの依存」を記憶する責任を移すためのエレガントなデザインパターンが存在する必要がaあり、パターンは基本的に何かより少ないものを含む必要があると感じますスマートポインタや本格的な参照カウントよりも複雑です。しかし、多分私は間違っています。たぶん、これはまさにそれらの参照カウントのスマートポインタの目的です。いずれにせよ、私は問題に対して適用する正しいパターンも、コードを修正するための最良の方法も知りません。

あなたが知っているなら、あなたはそれについて教えてくれますか?

これは、 Stackoverflowですでに気付いた最も関連性の高い回答です。しかし、私が理解していない1つか2つの単語を使用する以外に、その答えはとにかくこの質問に答えていないようです。

(私のコンパイラはまだC ++ 11をうまくサポートしていませんが、C ++ 11が私の問題に対処することを特に意図した機能をもたらす場合は、もちろんそれについて学ぶことに興味があるはずです。しかし、確かに、私の質問は主にOO /スコーピングの基本に関係します。質問は、これや最新のコンパイラの新機能よりも、基礎となるデザインパターンにさらに関心があります。)

読者への注意

いくつかの良い答えがこの質問を飾っています。Stackoverflowでは、ご存知のように、(今月または数年後に読んだときに)ベストアンサーを検索する必要がないように、質問者はベストアンサーを受け入れる責任があります。

ただし、この質問に最もよく答えるのは2つの答えの組み合わせです。あなたは両方を読むべきです:

  • @ MatthieuM。の回答は、共有所有権とオブザーバーパターンに関するものです。と
  • @JamesKanzeの回答は、オブザーバーパターンが優先される理由と時期に関するものです。
4

4 に答える 4

4

最初のオブジェクトが削除される理由によって異なります。誰かがそれを使用していないと誰かが思っているという理由だけで、スマートポインタの問題を回避できる可能性があります。それでもサイクルに注意する必要がありますが、動的割り当てを使用する理由が単に過度に高価なコピーを回避するためである場合は、オブジェクトはおそらく純粋なデータであり、ポインタがないため、安全です。

もちろん、はるかに多くの場合、オブジェクトが削除される理由は、プログラムロジックがオブジェクトを必要とするためであり、ある種のスマートポインターを使用して削除を延期すると、プログラムロジックが破損します。このような場合、オブジェクトへのポインタを持つクラスは、オブジェクトが削除されたことを通知する必要があります。これは結果としての行為です。この場合、オブザーバーパターンが標準ソリューションです。オブジェクトbはオブジェクトに登録さA れ、その終了が通知され、結果として必要なことは何でも実行します。BクラスがSessionで、Aクラスがであるという単純なケースを考えてみましょうConnection。実際の接続が切断された場合、Connectionクラスに通知され、自己破壊されます。(デストラクタで)そうすると、登録されているすべてのオブジェクトに通知されます。サーバーを使用している場合、Sessionクラスはおそらく問題をログに記録し、それ自体を自己破壊します。接続が切断されると、セッションが終了します。ただし、クライアントでは、Sessionクラスは新しいを作成しようとし、Connectionそれが失敗した場合にのみセッションを中止する場合があります。

于 2012-11-24T17:53:38.680 に答える
3

スマートな解決策は、複数のコピーではなく、1つのオブジェクトだけを保持する必要がある場合は、スマートポインターを使用することです。

C ++ 11では、次の選択肢から選択できます。

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

あなたの場合、私はあなたが2番目のものが必要だと思います:std::shared_ptr

于 2012-11-24T17:35:59.890 に答える
3

私は2つの特定の解決策を考えることができます:

  • 共有所有権
  • オブザーバーパターン

共有所有権ソリューションでは、の存続期間は所有者a数のカウンターによって決定されます。所有者がいなくなった場合にのみ、の存続期間は終了します。これは通常、を使用して実装されます。astd::shared_ptr<A>

オブザーバーソリューションでaは、に渡されるとb、それへの参照を保持することをa記憶します。b次に、a死ぬと、すぐに通知するか、次のアクセス試行で通知されるようにb「トークン」を残します。b

直接通知は通常、現在のリファラーのリストを維持し、破棄時に各リファラーを呼び出すことによって処理されます(参照を消去できるようにするため)。これは典型的なオブザーバーパターンです。

間接通知は通常、に到達するために通過するプロキシオブジェクトを持つことによって処理されますa。死ぬとプロキシにa通知され(O(1))、bアクセスを試みるとaプロキシを通過する必要があります。これを自分で実装することにはさまざまな困難があります。したがって、より良いアプローチは、標準の機能を使用することです。std::shared_ptr今回は、と組み合わせstd::weak_ptrます。

最後に、これらのソリューションは同等ではありません。

  • 共有所有権は、オブザーバースキームが最初に死ぬことを許可している間、生きているa間は死ぬことができないことを意味しますba
  • 直接通知により、の死bについてすぐに反応できますaが、同時にプログラムがより脆弱になる可能性があります(aのデストラクタの実行中は、例外をスローしないでください)。

あなた自身の毒を選んでください:)

于 2012-11-24T17:49:41.687 に答える
1

あなたが探しているパターンは、「あなたがオブジェクトを所有しているなら、それへの非所有の参照を与えて、それからそれを破壊しないでください」です。私はそれがより良い名前を持っているとは思いませんが、それは本当に良いC++エチケットです。オブジェクトを保持できるオブジェクトへのポインタを渡す場合、そのオブジェクトはポインタが有効であることに依存している可能性があることを知っておく必要があります。この状況では、オブジェクトの存続期間が非所有オブジェクトの存続期間よりも長生きすることを確認するだけです。

関数に渡していて、関数がグローバル状態を利用していないと想定できる場合は、心配する必要はありません。関数が戻ったら、そのポインターで実行されていると想定する必要があります。

Nawazが言ったように、これについて心配するのをやめる本当の方法は、適切な所有権セマンティクスを持つスマートポインターを使用することです。所有権を別のオブジェクトに譲渡する場合は、を使用しstd::unique_ptrます。所有権を共有する場合は、を使用しstd::shared_ptrます。所有していない生のポインタを使用したい場合は、使用しないとは言いませんが、少なくともそれが引き起こす可能性のある問題を知っています。


誤ってオブジェクトを早めに削除しないようにするに、次のようにします。

const std::unique_ptr<A> p(new A(my_int));
B b(p.get());
// the A object will be destroyed at the end of the current scope

ここでは、の(非常に最小限の!)オーバーヘッドはありませんstd::shared_ptr。本当にお勧めできるかわかりません。セミスマートよりもフルスマートにする方がはるかに望ましいです。は、const std::unique_ptrと同様の所有権セマンティクスを持っていますboost::scoped_ptr:「私はこれを所有し、他の誰も所有しません」と言います。reset違いは、のようにこれを呼び出すことができないことconst std::unique_ptrですboost::scoped_ptr

于 2012-11-24T17:47:47.393 に答える