16

過去 2 年間、プロジェクトでスマート ポインター (正確には boost::shared_ptr) を広く使用してきました。私はそれらの利点を理解し、高く評価しており、一般的にそれらをとても気に入っています. しかし、それらを使用すればするほど、プログラミング言語で気に入っているように見えるメモリ管理と RAII に関する C++ の決定論的な動作が恋しくなります。スマート ポインターは、メモリ管理のプロセスを簡素化し、とりわけ自動ガベージ コレクションを提供しますが、問題は、一般的に自動ガベージ コレクションを使用し、スマート ポインターを具体的に使用すると、初期化 (非) 化の順序である程度の不確定性が導入されることです。この不確定性は、プログラマーからコントロールを奪い、最近気付いたように、API の設計と開発の仕事をします。

さらに詳しく説明すると、現在 API を開発しています。この API の一部では、特定のオブジェクトを他のオブジェクトの前に初期化または破棄する必要があります。別の言い方をすれば、初期化 (非) 化の順序が重要な場合があります。簡単な例として、System というクラスがあるとします。システムは、いくつかの基本的な機能 (この例ではロギング) を提供し、スマート ポインターを介して多数のサブシステムを保持します。

class System {
public:
    boost::shared_ptr< Subsystem > GetSubsystem( unsigned int index ) {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ];
    }

    void LogMessage( const std::string& message ) {
        std::cout << message << std::endl;
    }

private:
    typedef std::vector< boost::shared_ptr< Subsystem > > SubsystemList;
    SubsystemList mSubsystems;    
};

class Subsystem {
public:
    Subsystem( System* pParentSystem )
         : mpParentSystem( pParentSystem ) {
    }

    ~Subsystem() {
         pParentSubsystem->LogMessage( "Destroying..." );
         // Destroy this subsystem: deallocate memory, release resource, etc.             
    }

    /*
     Other stuff here
    */

private:
    System * pParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
};

すでにおわかりのように、サブシステムはシステムのコンテキストでのみ意味があります。しかし、そのような設計のサブシステムは、その親システムよりも簡単に存続できます。

int main() {
    {
        boost::shared_ptr< Subsystem > pSomeSubsystem;
        {
            boost::shared_ptr< System > pSystem( new System );
            pSomeSubsystem = pSystem->GetSubsystem( /* some index */ );

        } // Our System would go out of scope and be destroyed here, but the Subsystem that pSomeSubsystem points to will not be destroyed.

     } // pSomeSubsystem would go out of scope here but wait a second, how are we going to log messages in Subsystem's destructor?! Its parent System is destroyed after all. BOOM!

    return 0;
}

サブシステムを保持するために生のポインターを使用していた場合、システムがダウンしたときにサブシステムを破棄していたでしょう。もちろん、pSomeSubsystem はダングリング ポインターになります。

クライアント プログラマを保護するのは API 設計者の仕事ではありませんが、API を正しく使いやすく、間違って使いにくくすることは良い考えです。だから私はあなたたちに尋ねています。どう思いますか?この問題をどのように緩和すればよいですか? そのようなシステムをどのように設計しますか?

前もって感謝します、ジョシュ

4

9 に答える 9

27

問題の概要

この質問には 2 つの競合する懸念があります。

  1. のライフサイクル管理Subsystem。適切なタイミングでの削除を可能にします。
  2. のクライアントは、使用している が有効Subsystemであることを知る必要がSubsystemあります。

取り扱い#1

Systemを所有しており、Subsystem独自のスコープでライフサイクルを管理する必要があります。これに s を使用shared_ptrすると、破棄が簡単になるので特に便利ですが、解放に関して求めている決定論が失われるため、それらを配布しないでください。

取り扱い#2

これは、取り組むべきより興味深い懸念事項です。Subsystem問題をより詳細に説明すると、 a のように動作するオブジェクト (およびそのSubsystem親) が存在するが、 aが破棄されたSystem後に適切に動作するオブジェクトをクライアントが受け取る必要があります。Subsystem

これは、Proxy パターンState パターン、およびNull オブジェクト パターンを組み合わせることで簡単に解決できます。これは解決策としては少し複雑に思えるかもしれませんが、「複雑さの反対側にしかないシンプルさがあります。」ライブラリ/API 開発者として、システムを堅牢にするためにさらに努力する必要があります。さらに、システムがユーザーの期待どおりに直感的に動作し、悪用しようとすると適切に減衰することを望んでいます。この問題には多くの解決策がありますが、これは、あなたとScott Meyersが言うように、「正しく使用するのは簡単で、間違って使用するのは難しい」というすべての重要なポイントに到達するはずです。

ここで、実際には が s のSystem何らかの基本クラスを扱いSubsystem、そこからさまざまなSubsystems が派生すると仮定しています。として以下に紹介しましたSubsystemBase以下のProxyオブジェクトを導入する必要があります。このオブジェクトは、プロキシしているオブジェクトにリクエストを転送することによってSubsystemProxyのインターフェイスを実装します。(この意味で、これはDecorator PatternSubsystemBaseの特別な目的のアプリケーションに非常によく似ています。) それぞれがこれらのオブジェクトの 1 つを作成し、 を介して保持し、 を介して要求されたときに戻ります。これは、 が呼び出されたときに親オブジェクトによって呼び出されます。Subsystemshared_ptrGetProxy()SystemGetSubsystem()

System範囲外になると、そのSubsystemオブジェクトのそれぞれが破壊されます。デストラクタで を呼び出しますmProxy->Nullify()。これにより、ProxyオブジェクトのStateが変更されます。これは、インターフェイスを実装するNull Objectを指すように変更することによって行われSubsystemBaseますが、何もしないことによって行われます。

ここでState パターンを使用すると、クライアント アプリケーションは、特定のパターンが存在するかどうかを完全に認識できなくなりSubsystemます。さらに、ポインターをチェックしたり、破棄されたはずのインスタンスを保持したりする必要はありません。

プロキシ パターンを使用すると、クライアントは、API の内部動作の詳細を完全にラップし、一定の統一されたインターフェイスを維持する軽量オブジェクトに依存することができます。

Null オブジェクト パターンを使用すると、元のオブジェクトが削除された後でもプロキシを機能させることができます。Subsystem

サンプルコード

大まかな疑似コード品質の例をここに載せましたが、満足できませんでした。上記で説明したことの正確なコンパイル(g ++を使用)の例になるように書き直しました。これを機能させるには、他のいくつかのクラスを紹介する必要がありましたが、それらの用途は名前から明らかなはずです。クラスに シングルトン パターンを採用しました。はプロキシ動作を から完全に抽象化し、この動作を無視できるようにします。クラスの UML ダイアグラムは次のとおりです。NullSubsystemProxyableSubsystemBaseSubsystem

サブシステムとシステム階層の UML ダイアグラム

コード例:

#include <iostream>
#include <string>
#include <vector>

#include <boost/shared_ptr.hpp>


// Forward Declarations to allow friending
class System;
class ProxyableSubsystemBase;

// Base defining the interface for Subsystems
class SubsystemBase
{
  public:
    // pure virtual functions
    virtual void DoSomething(void) = 0;
    virtual int GetSize(void) = 0;

    virtual ~SubsystemBase() {} // virtual destructor for base class
};


// Null Object Pattern: an object which implements the interface to do nothing.
class NullSubsystem : public SubsystemBase
{
  public:
    // implements pure virtual functions from SubsystemBase to do nothing.
    void DoSomething(void) { }
    int GetSize(void) { return -1; }

    // Singleton Pattern: We only ever need one NullSubsystem, so we'll enforce that
    static NullSubsystem *instance()
    {
      static NullSubsystem singletonInstance;
      return &singletonInstance;
    }

  private:
    NullSubsystem() {}  // private constructor to inforce Singleton Pattern
};


// Proxy Pattern: An object that takes the place of another to provide better
//   control over the uses of that object
class SubsystemProxy : public SubsystemBase
{
  friend class ProxyableSubsystemBase;

  public:
    SubsystemProxy(SubsystemBase *ProxiedSubsystem)
      : mProxied(ProxiedSubsystem)
      {
      }

    // implements pure virtual functions from SubsystemBase to forward to mProxied
    void DoSomething(void) { mProxied->DoSomething(); }
    int  GetSize(void) { return mProxied->GetSize(); }

  protected:
    // State Pattern: the initial state of the SubsystemProxy is to point to a
    //  valid SubsytemBase, which is passed into the constructor.  Calling Nullify()
    //  causes a change in the internal state to point to a NullSubsystem, which allows
    //  the proxy to still perform correctly, despite the Subsystem going out of scope.
    void Nullify()
    {
        mProxied=NullSubsystem::instance();
    }

  private:
      SubsystemBase *mProxied;
};


// A Base for real Subsystems to add the Proxying behavior
class ProxyableSubsystemBase : public SubsystemBase
{
  friend class System;  // Allow system to call our GetProxy() method.

  public:
    ProxyableSubsystemBase()
      : mProxy(new SubsystemProxy(this)) // create our proxy object
    {
    }
    ~ProxyableSubsystemBase()
    {
      mProxy->Nullify(); // inform our proxy object we are going away
    }

  protected:
    boost::shared_ptr<SubsystemProxy> GetProxy() { return mProxy; }

  private:
    boost::shared_ptr<SubsystemProxy> mProxy;
};


// the managing system
class System
{
  public:
    typedef boost::shared_ptr< SubsystemProxy > SubsystemHandle;
    typedef boost::shared_ptr< ProxyableSubsystemBase > SubsystemPtr;

    SubsystemHandle GetSubsystem( unsigned int index )
    {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ]->GetProxy();
    }

    void LogMessage( const std::string& message )
    {
        std::cout << "  <System>: " << message << std::endl;
    }

    int AddSubsystem( ProxyableSubsystemBase *pSubsystem )
    {
      LogMessage("Adding Subsystem:");
      mSubsystems.push_back(SubsystemPtr(pSubsystem));
      return mSubsystems.size()-1;
    }

    System()
    {
      LogMessage("System is constructing.");
    }

    ~System()
    {
      LogMessage("System is going out of scope.");
    }

  private:
    // have to hold base pointers
    typedef std::vector< boost::shared_ptr<ProxyableSubsystemBase> > SubsystemList;
    SubsystemList mSubsystems;
};

// the actual Subsystem
class Subsystem : public ProxyableSubsystemBase
{
  public:
    Subsystem( System* pParentSystem, const std::string ID )
      : mParentSystem( pParentSystem )
      , mID(ID)
    {
         mParentSystem->LogMessage( "Creating... "+mID );
    }

    ~Subsystem()
    {
         mParentSystem->LogMessage( "Destroying... "+mID );
    }

    // implements pure virtual functions from SubsystemBase
    void DoSomething(void) { mParentSystem->LogMessage( mID + " is DoingSomething (tm)."); }
    int GetSize(void) { return sizeof(Subsystem); }

  private:
    System * mParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
    std::string mID;
};



//////////////////////////////////////////////////////////////////
// Actual Use Example
int main(int argc, char* argv[])
{

  std::cout << "main(): Creating Handles H1 and H2 for Subsystems. " << std::endl;
  System::SubsystemHandle H1;
  System::SubsystemHandle H2;

  std::cout << "-------------------------------------------" << std::endl;
  {
    std::cout << "  main(): Begin scope for System." << std::endl;
    System mySystem;
    int FrankIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Frank"));
    int ErnestIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Ernest"));

    std::cout << "  main(): Assigning Subsystems to H1 and H2." << std::endl;
    H1=mySystem.GetSubsystem(FrankIndex);
    H2=mySystem.GetSubsystem(ErnestIndex);


    std::cout << "  main(): Doing something on H1 and H2." << std::endl;
    H1->DoSomething();
    H2->DoSomething();
    std::cout << "  main(): Leaving scope for System." << std::endl;
  }
  std::cout << "-------------------------------------------" << std::endl;
  std::cout << "main(): Doing something on H1 and H2. (outside System Scope.) " << std::endl;
  H1->DoSomething();
  H2->DoSomething();
  std::cout << "main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object." << std::endl;

  return 0;
}

コードからの出力:

main(): Creating Handles H1 and H2 for Subsystems.
-------------------------------------------
  main(): Begin scope for System.
  <System>: System is constructing.
  <System>: Creating... Frank
  <System>: Adding Subsystem:
  <System>: Creating... Ernest
  <System>: Adding Subsystem:
  main(): Assigning Subsystems to H1 and H2.
  main(): Doing something on H1 and H2.
  <System>: Frank is DoingSomething (tm).
  <System>: Ernest is DoingSomething (tm).
  main(): Leaving scope for System.
  <System>: System is going out of scope.
  <System>: Destroying... Frank
  <System>: Destroying... Ernest
-------------------------------------------
main(): Doing something on H1 and H2. (outside System Scope.)
main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object.

他の考え:

  • Game Programming Gems の本の 1 つで読んだ興味深い記事で、デバッグと開発に Null オブジェクトを使用する方法について説明しています。彼らは特に、チェッカーボード テクスチャなどの Null グラフィックス モデルとテクスチャを使用して、欠落しているモデルを目立たせることについて話していました。呼び出しと、アクセスされるたびに呼び出しスタックをログに記録するNullSubsystemfor を変更することで、ここでも同じことが適用できます。ReportingSubsystemこれにより、あなたまたはあなたのライブラリのクライアントは、スコープ外になったものに依存している場所を追跡できますが、クラッシュを引き起こす必要はありません。

  • @Arkadiy のコメントで、彼が持ち出した循環依存関係は少し不快だSystemと述べました。これは、Robert C Martin のDependency Inversion Principleを適用して、依存するインターフェイスから派生Subsystemさせることで簡単に修正できます。s が必要とする機能を親から分離し、そのためのインターフェイスを作成し、そのインターフェイスの実装者を保持してs に渡し、 . たとえば、ログへの書き込みに使用する があり、それから派生したり、そこからインスタンスを保持したりできます。SystemSubsystemSubsystemSystemSubsystemshared_ptrLoggerInterfaceSubsystemCoutLoggerFileLoggerSystem
    循環依存の排除

于 2008-12-30T22:43:05.570 に答える
11

これは、weak_ptrクラスを適切に使用することで実行できます。実際、あなたはすでに良い解決策にかなり近づいています。クライアント プログラマーを「考え抜く」ことを期待することはできませんし、API の「ルール」に常に従うことを期待するべきでもありません (既に認識していると思います)。したがって、あなたが本当にできる最善のことは、ダメージコントロールです。

クライアント開発者がポインターへの参照を常に要求することなくポインターの有効性をテストできるように、単にaではなく aをGetSubsystem返すように呼び出しを行うことをお勧めします。weak_ptrshared_ptr

同様に、onへの呼び出しとfor のチェックを介して親がまだ存在するかどうかを内部的に検出できるようにするpParentSystem必要があります (生のポインターはこれを通知しません)。boost::weak_ptr<System>SystemlockpParentSystemNULL

Subsystem対応するオブジェクトが存在するかどうかを常にチェックするようにクラスを変更すると仮定するとSystem、クライアント プログラマーが目的のスコープ外でオブジェクトを使用しようとすると、説明のつかないSubsystem例外ではなく、(ユーザーが制御する) エラーが発生することを保証できます。 (クライアントプログラマーがキャッチ/適切に処理することを信頼する必要があります)。

したがって、あなたの例ではmain()、物事はブームになりません! の dtorでこれを処理する最も適切な方法は、Subsystem次のようにすることです。

class Subsystem
{
...
  ~Subsystem() {
       boost::shared_ptr<System> my_system(pParentSystem.lock());

       if (NULL != my_system.get()) {  // only works if pParentSystem refers to a valid System object
         // now you are guaranteed this will work, since a reference is held to the System object
         my_system->LogMessage( "Destroying..." );
       }
       // Destroy this subsystem: deallocate memory, release resource, etc.             

       // when my_system goes out of scope, this may cause the associated System object to be destroyed as well (if it holds the last reference)
  }
...
};

これが役立つことを願っています!

于 2008-12-30T18:52:18.907 に答える
4

ここで、システムは明らかにサブシステムを所有しており、所有権を共有しても意味がないと思います。私は単に生のポインターを返します。サブシステムがそのシステムよりも長く存続する場合、それ自体がエラーです。

于 2008-12-30T18:22:45.953 に答える
3

あなたは最初の段落の冒頭で正しかった。RAII に基づく設計 (私のものや最もよく書かれた C++ コードのようなもの) では、オブジェクトが排他的所有権ポインターによって保持されている必要があります。Boost では、scoped_ptr になります。

なぜscoped_ptrを使わなかったのですか?これは、weak_ptr の利点をダングリング リファレンスから保護したかったためですが、weak_ptr は shared_ptr でのみポイントできるためです。そのため、単一の所有権が本当に必要な場合に、shared_ptr を適切に宣言するという一般的な慣行を採用しています。これは誤った宣言であり、あなたが言うように、デストラクタが正しい順序で呼び出されることを危うくします。もちろん、所有権を共有したことがない場合は、それを回避できますが、共有されていないことを確認するために、すべてのコードを常にチェックする必要があります。

さらに悪いことに、boost::weak_ptr は使いにくい (-> 演算子がない) ため、プログラマーは受動的な参照を shared_ptr として誤って宣言することで、この不便さを回避しています。これはもちろん所有権を共有し、その shared_ptr を null にするのを忘れた場合、オブジェクトは破棄されたり、意図したときにそのデストラクタが呼び出されたりしません。

要するに、boost ライブラリにだまされてしまったということです。これは、C++ プログラミングの優れたプラクティスを採用できず、プログラマがその利点を引き出すために誤った宣言を行うことを余儀なくさせているのです。これは、所有権の共有が必要であり、正しい順序で呼び出されるメモリやデストラクタを厳密に制御することに関心がない、グルー コードをスクリプト化する場合にのみ役立ちます。

私はあなたと同じ道をたどってきました。C++ ではダングリング ポインターに対する保護が非常に必要ですが、boost ライブラリは許容できる解決策を提供しません。私はこの問題を解決しなければなりませんでした。私のソフトウェア部門は、C++ を安全にできるという保証を求めていました。だから私は自分自身をロールバックしました - それは非常に多くの仕事でした.

http://www.codeproject.com/KB/cpp/XONOR.aspx

シングルスレッドの作業には十分であり、スレッド間で共有されるポインターを受け入れるように更新しようとしています。その主な機能は、排他的に所有されているオブジェクトのスマート (自己ゼロ化) パッシブ オブザーバーをサポートすることです。

残念なことに、プログラマーはガベージ コレクションと「万能型」のスマート ポインター ソリューションに誘惑されており、大部分は所有権や受動的なオブザーバーについてさえ考えていません。文句を言うな。ブーストに対する異端はほとんど聞いたことがありません!

あなたに提案された解決策はとてつもなく複雑で、まったく役に立ちません。これらは、正しく宣言されなければならない明確な役割を持つオブジェクト ポインターと、Boost が解決策であるに違いないという盲目的な信念を認識することへの文化的な抵抗から生じる不条理の例です。

于 2009-08-05T13:23:51.847 に答える
2

System::GetSubsystem が生のポインター (スマート ポインターではなく) をサブシステムに返すことに問題はないと思います。クライアントはオブジェクトの構築を担当しないため、クライアントがクリーンアップを担当するという暗黙の契約はありません。また、これは内部参照であるため、Subsystem オブジェクトの有効期間は System object の有効期間に依存していると想定するのが妥当です。次に、この暗黙の契約を文書化して強化する必要があります。

ポイントは、所有権を再割り当てまたは共有していないということです。では、なぜスマートポインターを使用するのでしょうか?

于 2008-12-30T18:17:52.610 に答える
2

ここでの本当の問題は、あなたのデザインです。モデルは優れた設計原則を反映していないため、適切な解決策はありません。私が使用する便利な経験則は次のとおりです。

  • オブジェクトが他のオブジェクトのコレクションを保持しており、そのコレクションから任意のオブジェクトを返すことができる場合は、そのオブジェクトをデザインから削除してください

あなたの例は不自然だと思いますが、それは私が仕事でよく目にするアンチパターンです。自問してみてください、System追加していない価値は何std::vector< shared_ptr<SubSystem> >ですか? API のユーザーはSubSystem(API を返すため) のインターフェイスを知る必要があるため、それらのホルダーを作成することは複雑さを増すだけです。少なくとも人々は へのインターフェイスを知っており、上記のstd::vectorことを強制的に覚えさせたり、単に意地悪だけです。GetSubsystem()at()operator[]

あなたの質問はオブジェクトの有効期間の管理に関するものですが、オブジェクトの配布を開始すると、他の人がそれらを存続させることを許可して有効期間を制御できなくなるか ( shared_ptr)、オブジェクトがなくなった後に使用するとクラッシュする危険性があります (生のポインター)。マルチスレッド アプリでは、さらに悪いことに、別のスレッドに渡したオブジェクトを誰がロックするのでしょうか? ブーストの共有ポインターとウィーク ポインターは、この方法で使用すると複雑さを誘発するトラップになります。

ホルダーを作成する場合は、複雑さをユーザーから隠し、自分で管理できる負担から解放する必要があります。例として、a) コマンドをサブシステムに送信する (例: URI - /system/subsystem/command?param=value)、および b) サブシステムとサブシステム コマンドを (stl のような反復子を介して) 反復する、および場合によっては c) で構成されるインターフェースregister サブシステムを使用すると、実装の詳細のほとんどすべてをユーザーから隠し、有効期間/順序付け/ロックの要件を内部的に強制することができます。

どのような場合でも、反復可能/列挙可能な API は、オブジェクトを公開するよりもはるかに優れています。コマンド/登録は、テスト ケースまたは構成ファイルを生成するために簡単にシリアル化でき、インタラクティブに表示できます (たとえば、ツリー コントロールで、クエリによって作成されたダイアログを使用)。利用可能なアクション/パラメーター)。また、サブシステム クラスに必要な内部変更から API ユーザーを保護することにもなります。

アーロンの回答のアドバイスに従わないように注意します。実装するのに 5 つの異なる設計パターンが必要な、これほど単純な問題の解決策を設計することは、間違った問題が解決されていることを意味するだけです。また、マイヤーズ氏自身が認めたように、デザインに関してマイヤーズ氏を引用する人にもうんざりしています。

「私は 20 年以上製品ソフトウェアを作成しておらず、C++ で製品ソフトウェアを作成したこともありません。いや、一度もありません。さらに、C++ で製品ソフトウェアを作成しようとしたことさえありません。 C++ 開発者、私は志望者でもありません. これをわずかに相殺するのは、私が大学院時代 (1985 ~ 1993 年) に研究用ソフトウェアを C++ で作成したという事実ですが、それでも小規模 (数千行) の単一開発者でした。そして、12 年以上前にコンサルタントとして働き始めて以来、私の C++ プログラミングは、「これがどのように機能するか見てみよう」(または、「コンパイラがいくつあるか見てみよう」) というおもちゃに限定されていました。これは壊れます」)プログラム、通常は単一のファイルに収まるプログラム」.

彼の本が読むに値しないというわけではありませんが、彼にはデザインや複雑さについて話す権限がありません。

于 2009-05-30T03:33:25.923 に答える
1

vector<Subsystem>あなたの例では、システムがではなく を保持していた方が良いでしょうvector<shared_ptr<Subsystem> >。その両方がよりシンプルであり、あなたの懸念を解消します。GetSubsystem は代わりに参照を返します。

于 2008-12-30T18:16:33.737 に答える
1

スタック オブジェクトは、インスタンス化された順序とは逆の順序で解放されるため、API を使用する開発者がスマート ポインターを管理しようとしない限り、通常は問題になりません。防ぐことができないことがいくつかありますが、できることは実行時に警告を出すことであり、できればデバッグのみを行うことです。

あなたの例は私にはCOMに非常に似ているようです。shared_ptrを使用して返されるサブシステムで参照カウントがありますが、システムオブジェクト自体にはそれがありません。

各サブシステム オブジェクトが、作成時にシステム オブジェクトに対して addref を実行し、破棄時にリリースを実行した場合、システム オブジェクトが早期に破棄されたときに参照カウントが正しくない場合、少なくとも例外を表示できます。

また、weak_ptr を使用すると、代わりにメッセージを提供したり、物事が間違った順序で解放されたときに爆発したりすることもできます。

于 2008-12-31T00:43:36.730 に答える
0

問題の本質は循環参照です。システムはサブシステムを参照し、サブシステムはシステムを参照します。この種のデータ構造は、参照カウントでは簡単に処理できません。適切なガベージ コレクションが必要です。エッジの 1 つに生のポインターを使用してループを中断しようとしています。これは複雑さを増すだけです。

少なくとも 2 つの優れた解決策が提案されているので、以前のポスターを凌駕しようとはしません。@Aaronのソリューションでは、サブシステムではなくシステムのプロキシを使用できることに注意してください-より複雑なものと意味のあるものに依存します。

于 2008-12-31T14:53:38.857 に答える