Frage

Ich habe mit großem Umfang intelligente Zeiger (boost :: shared_ptr genau zu sein) in meinen Projekten in den letzten zwei Jahren. Ich verstehe und schätzen ihre Vorteile und ich sie im Allgemeinen sehr gut gefällt. Aber je mehr ich sie verwenden, desto mehr vermisse ich die deterministisches Verhalten von C ++ mit Bezug auf die Speicherverwaltung und RAII, die ich scheinen in einer Programmiersprache zu mögen. Smart-Pointer vereinfacht den Prozess der Speicherverwaltung und automatische unter anderem Garbage Collection zur Verfügung stellen, aber das Problem ist, dass mit Hilfe der automatische Garbage Collection im Allgemeinen und Smart-Pointer speziell ein gewisses Maß an indeterminisim in der Größenordnung von (de) Initialisierungen einführt. Diese indeterminism nimmt die Kontrolle weg von den Programmierern und, wie ich gekommen bin in letzter Zeit zu realisieren, macht die Aufgabe der Gestaltung und Entwicklung von APIs, die Verwendung von der nicht vollständig im Voraus zum Zeitpunkt der Entwicklung, ärgerlicherweise zeitaufwendig, da bekannt ist, alle Nutzungsmuster und Eckfällen muss auch daran gedacht werden.

Um mehr zu erarbeiten, ich bin zur Zeit eine API zu entwickeln. Teile dieser API erfordert bestimmte Objekte vor oder nach anderen Objekten zerstört initialisiert werden. Anders ausgedrückt, die Reihenfolge der (de) Initialisierung ist manchmal wichtig. Um Ihnen ein einfaches Beispiel, sagen wir, wir haben eine Klasse namens-System. Ein System, bietet einige grundlegende Funktionalität (in unserem Beispiel der Anmeldung) und hält eine Reihe von Subsystemen über intelligente Zeiger.

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
};

Wie Sie bereits wissen, ist ein Subsystem nur sinnvoll, im Rahmen eines Systems. Aber ein Subsystem in einer solchen Konstruktion kann die Mutter-System leicht überleben.

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;
}

Wenn wir rohe Zeiger benutzt hatte Subsysteme zu halten, würden wir Subsysteme zerstört, wenn unser System untergegangen war, natürlich dann, pSomeSubsystem ein baumelnden Zeiger wäre.

Obwohl, es ist nicht die Aufgabe eines API-Designer die Client-Programmierer vor sich selbst zu schützen, ist es eine gute Idee, die API einfach zu bedienen korrekt und schwer zu bedienen falsch zu machen. So frage ich euch. Was denkst du? Wie soll ich dieses Problem lindern? Wie würden Sie ein solches System entwerfen?

Vielen Dank im Voraus, Josh

War es hilfreich?

Lösung

Problem Zusammenfassung

Es gibt zwei konkurrierende Bedenken in dieser Frage.

  1. Life-Cycle-Management von Subsystems, deren Entfernung zur richtigen Zeit zu ermöglichen.
  2. Kunden von Subsystems müssen wissen, dass die Subsystem sie verwenden gültig ist.

Handhabung # 1

System besitzt die Subsystems und soll ihren Lebenszyklus mit einem eigenen Rahmen verwalten. Mit shared_ptrs hierfür ist besonders nützlich, da sie die Zerstörung vereinfacht, aber man sollte sich nicht aus aushändigen, weil dann verlieren Sie den Determinismus Sie im Hinblick auf ihre Freigabe suchen.

Handhabung # 2

Dies ist umso interessante Anliegen anzusprechen. Beschreibung des Problems genauer, müssen Sie Kunden ein Objekt zu empfangen, die während dieser Subsystem wie ein Subsystem verhält (und es ist Eltern System) vorhanden ist, verhält sich aber in geeigneter Weise nach einer Subsystem zerstört wird.

Dies ist leicht durch eine Kombination aus dem Proxy Pattern , die State-Muster und die Null-Objekt-Muster . Während dies erscheinen mag ein wenig komplex eine Lösung sein, ‚ Es gibt eine Einfachheit nur auf der anderen Seite der Komplexität werden mußte.‘ Als Bibliothek / API-Entwickler, müssen wir die extra Meile gehen, um unsere Systeme robuster zu machen. Des Weiteren wollen wir unsere Systeme intuitiv verhalten, wie ein Benutzer erwartet, und anmutig zerfallen, wenn sie sie zu missbrauchen versuchen. viele Lösungen für dieses Problem gibt es jedoch sollte diese von Ihnen, dass alle wichtigen Punkt, wo, wie Sie und Scott Meyers sagen, es ist „ einfach richtig und schwer zu bedienen falsch zu verwenden

Nun, ich gehe davon aus, dass in Wirklichkeit System Angebote in irgendeiner Basisklasse von Subsystems, aus dem Sie verschiedene Subsystems ableiten. Ich habe es eingeführt unten als SubsystemBase. Sie benötigen ein Proxy Objekt, SubsystemProxy unten einzuführen, die die Schnittstelle von SubsystemBase implementiert durch Anfragen an das Objekt weiterzuleiten ist Proxying. (In diesem Sinne ist es sehr ähnlich wie ein Zweck Anwendung des Decorator-Muster ). Jede Subsystem eines dieser Objekte erzeugt, die es über einen shared_ptr hält, und kehrt zurück, wenn über GetProxy() angefordert, die von der Mutter System Objekt aufgerufen wird, wenn GetSubsystem() aufgerufen wird.

Wenn ein System den Gültigkeitsbereich verlässt, jede davon ist Subsystem Objekte wird zerstört. In ihrem destructor, nennen sie mProxy->Nullify(), die ihre verursacht Proxy Objekte ihre ändern Staat . Sie tun dies auf ein Punkt durch Ändern Null-Objekt , die den SubsystemBase-Schnittstelle implementiert, tut dies aber durch nichts zu tun.

Mit dem State-Muster hier erlaubt hat, die Client-Anwendung völlig blind zu sein, ob eine bestimmte Subsystem existiert. Darüber hinaus braucht es keine Zeiger zu überprüfen oder Instanzen halten um die zerstört werden soll.

Das Proxy-Muster kann der Client auf einem leichtes Objekt abhängig sein, dassvollständig umschließt die Details der API Innenleben auf und sorgt für eine konstante, einheitliche Schnittstelle.

Das Null-Objekt-Muster können Sie die Proxy funktionieren, nachdem der ursprüngliche Subsystem entfernt worden ist.

Beispielcode

Ich hatte ein grobes Pseudo-Code-Qualität Beispiel hier gegeben, aber ich war nicht zufrieden mit ihm. Ich habe es neu geschrieben ein um genau zu sein, Kompilieren (I verwendet g ++) Beispiel dafür, was ich oben beschrieben habe. Um es zu arbeiten, hatte ich ein paar andere Klassen einzuführen, aber ihre Verwendung sollte aus ihrem Namen klar sein. Ich verwendet, um die Singleton Pattern für die NullSubsystem Klasse, wie es macht Sinn, dass Sie mehr als eine nicht brauchen würden. ProxyableSubsystemBase völlig abstrahiert das Proxying Verhalten vom Subsystem, so dass sie nichts von diesem Verhalten sein. Hier ist das UML-Diagramm der Klassen:

Beispielcode:

#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;
}

Ausgabe aus dem Code:

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.

Andere Gedanken:

  • Ein interessanter Artikel, den ich über die Verwendung von Null-Objekte für das Debuggen und Entwicklung in einer der Game Programming Gems Bücher Gespräche lesen. Sie sprachen speziell über die Verwendung von Null Graphics Modelle und Texturen, wie ein Schachbrett Textur fehlt Modelle wirklich abheben. Das gleiche könnte hier angewandt, indem die NullSubsystem für ein ReportingSubsystem Wechsel aus, die das Anrufprotokoll würde und möglicherweise die Aufrufhierarchie, wenn sie zugegriffen wird. Dies würde es ermöglichen Ihnen oder Kunden Ihrer Bibliothek ausfindig zu machen, wo sie auf etwas abhängig, die außerhalb des Bereichs gegangen ist, aber ohne die Notwendigkeit, einen Absturz zu verursachen.

  • erwähnte ich in einem Kommentar @Arkadiy, dass die zirkuläre Abhängigkeit er zwischen System und Subsystem brachte ein bisschen unangenehm ist. Es kann leicht durch System mit von einer Schnittstelle ableiten behoben werden, auf dem Subsystem, eine Anwendung von Robert C Martin Dependency Inversion Principle . Noch besser wäre es, die Funktionalität zu isolieren, die Subsystems von ihren Eltern brauchen, schreiben Sie eine Schnittstelle für das, dann auf einen Implementierer dieser Schnittstelle in System halten und an die Subsystems geben, die sie über einen shared_ptr halten würden. Zum Beispiel könnten Sie LoggerInterface haben, die Ihre Subsystem in das Protokoll schreiben verwendet, dann könnte man CoutLogger oder FileLogger daraus ableiten und eine Instanz eines solchen in System halten.
    die Beseitigung der zirkulären Abhängigkeit

Andere Tipps

Dies ist machbar mit der richtigen Verwendung der weak_ptr Klasse. In der Tat sind Sie schon ganz in der Nähe, eine gute Lösung zu haben. Sie haben Recht, dass Sie nicht Ihren Client-Programmierer erwartet werden können „out-denken“, noch sollte man erwarten, dass sie immer den „Regeln“ Ihre API folgen (wie ich bin sicher, dass Sie bereits wissen). Also, das Beste, was Sie wirklich tun können, ist Schadensbegrenzung.

Ich empfehle Ihren Anruf mit GetSubsystem einem weak_ptr zurückzukehren, anstatt einem shared_ptr einfach so, dass der Client-Entwickler die Gültigkeit des Zeigers testen kann, ohne immer einen Verweis auf sie zu behaupten.

In ähnlicher Weise haben pParentSystem ein boost::weak_ptr<System> sein, so dass es intern, ob noch seine Eltern erkennen kann System über einen Aufruf existiert auf lock zusammen mit einem Scheck für pParentSystem (a Rohzeiger wird Ihnen nicht sagen, diese) NULL.

Angenommen, Sie Ihre Subsystem Klasse ändern immer zu prüfen, ob nicht sein entsprechendes System Objekt vorhanden ist, können Sie sicherstellen, dass, wenn der Client-Programmierer versucht, außerhalb des beabsichtigten Umfangs das Subsystem Objekt zu verwenden, dass ein Fehler führen wird (die Sie steuern) , eher als eine unerklärliche Ausnahme (dass Sie die Client-Programmierer vertrauen muss / richtig zu behandeln zu fangen).

Also, in Ihrem Beispiel mit main(), die Dinge nicht gehen BOOM! Die anmutige Art und Weise dies in der Subsystem des dtor zu handhaben wäre, um es in etwa so aussehen hat:

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)
  }
...
};

Ich hoffe, das hilft!

Hier besitzt Systems deutlich die Subsysteme und ich sehe keinen Sinn in gemeinsamen Besitz haben. Ich würde einfach einen rohen Zeiger zurück. Wenn ein Subsystem seiner System-überlebt, das ist ein Fehler auf seinem eigenen.

Sie waren direkt am Anfang in Ihrem ersten Absatz. Ihre Entwürfe basieren auf RAII (wie bei mir und den meisten gut geschrieben C ++ Code) verlangen, dass Ihre Objekte durch exklusive Eigentums Zeiger gehalten werden. In Boost-die scoped_ptr würde.

Also, warum Sie nicht scoped_ptr verwenden. Es wird sicher sein, weil Sie die Vorteile von weak_ptr zum Schutz gegen die baumelnden Referenzen wollte, aber Sie können nur eine weak_ptr an einem Shared_ptr zeigen. So haben Sie die gängige Praxis der zweckmäßigerweise erklärt Shared_ptr angenommen, wenn, was Sie wirklich war einzigen Besitz wollte. Dies ist eine falsche Erklärung und wie Sie sagen, es Kompromisse Destruktoren in der richtigen Reihenfolge aufgerufen wird. Natürlich, wenn Sie jemals nie das Eigentum teilen Sie damit durchkommen -. Aber Sie werden ständig alle Ihre Code überprüfen müssen um sicherzustellen, dass es zu machen war nie geteilt

Um die Sache noch schlimmer machen, die boost :: weak_ptr unbequem zu bedienen ist (es hat keinen Operator ->) so Programmierer diesen Nachteil zu vermeiden, indem fälschlicherweise erklären passive Beobachtung Referenzen als shared_ptr. Dies ist natürlich Aktien Eigentum und wenn Sie vergessen, auf null dass Shared_ptr dann Ihrem Objekt nicht erhalten zerstört oder sein destructor aufgerufen, wenn Sie beabsichtigen, es zu.

Kurz gesagt: Sie wurden von der Boost-Bibliothek schaf - es scheitert Programmierer gut C ++ Programmierpraktiken und Kräfte zu umarmen falsche Angaben zu machen, um einen Nutzen daraus zu versuchen und zu leiten. Es ist nur für Skripting Glue Code nützlich, die wirklich gemeinsame Verantwortung will und interessiert sich nicht für eine strenge Kontrolle über Speicher oder Destruktoren in der richtigen Reihenfolge aufgerufen werden.

Ich habe den gleichen Weg wie Sie unten gewesen. Schutz gegen baumelnden Zeigern ist schlecht in C ++ benötigt aber die Boost-Bibliothek bietet keine akzeptable Lösung. Ich hatte dieses Problem zu lösen - meine Software-Abteilung wollte versichert, dass C ++ sicher gemacht werden kann. So rollte ich mein eigenes - es war ziemlich viel Arbeit und finden Sie unter:

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

Es ist völlig ausreichend für Single-Threaded-Arbeit, und ich bin über sie aktualisieren Zeiger wird geteilt über Threads zu umarmen. Sein Hauptmerkmal ist, dass es klug (Selbst Nullung) passive Beobachter der ausschließlich im Besitz Objekte.

unterstützt

Leider haben sich Programmierer von der Garbage Collection verführt und ‚one size fits all‘ Smart-Pointer-Lösungen und zu einem großen Teil denken nicht einmal über das Eigentum und passive Beobachter - als Folge sie wissen nicht einmal, dass das, was sie tun, ist falsch und nicht beschweren. Ketzerei gegen Boost ist fast unerhört!

Die Lösungen, die Ihnen vorgeschlagen wurden, sind absurd kompliziert und keine Hilfe überhaupt. Sie sind Beispiele für die Absurdität, die von einer kulturellen Zurückhaltung führt zu erkennen, dass Objektzeiger unterschiedliche Rollen haben, die korrekt deklariert werden müssen und einen blinden Glauben, der die Lösung seine Erhöhung muss.

Ich sehe nicht ein Problem mit dem System mit :: GetSubsystem einen rohen Zeiger zurückgeben (und nicht als ein Smart-Pointer) zu einem Subsystem. Da der Client für den Bau der Objekte nicht verantwortlich ist, dann gibt es keinen impliziten Vertrag für die Kunden für die Sanierung verantwortlich zu sein. Und da es sich um eine interne Referenz ist, sollte es vernünftig anzunehmen, dass die Lebensdauer des Subsystem Objekt ist abhängig von der Lebensdauer des Systemobjekts. Sie sollten dann diesen implizierten Vertrag verstärken mit Dokumentation so viel besagt.

Der Punkt ist, dass Sie nicht neu zuweisen oder das Eigentum teilen - warum also einen intelligenten Zeiger verwendet

?

Das eigentliche Problem hier ist Ihr Design. Es gibt keine schöne Lösung, weil das Modell nicht gut Design-Prinzipien reflektiert. Hier ist eine handliche Faustregel ich benutze:

  • Wenn ein Objekt eine Sammlung von anderen Objekten hält, und kann jedes beliebiges Objekt aus dieser Sammlung zurück, dann entfernen Sie dieses Objekt aus Ihrem Design .

Ich weiß, dass Ihr Beispiel gekünstelt ist, aber es ist ein Anti-Muster, das ich viel bei der Arbeit zu sehen. Fragen Sie sich, welchen Wert System hinzufügt, dass std::vector< shared_ptr<SubSystem> > tut? Benutzer des API benötigen die Schnittstelle von SubSystem kennen (da Sie sie zurück), so einen Halter Schreiben für sie nur die Komplexität zu erhöhen ist. Wenigstens wissen die Menschen die Schnittstelle std::vector, sie zu zwingen, GetSubsystem() oben at() oder operator[] zu erinnern, ist nur bedeutet .

Ihre Frage ist über das Objekt Lebensdauer verwalten, aber wenn man das Austeilen Objekte beginnen, entweder Sie die Kontrolle über die gesamte Lebensdauer verlieren, indem sie andere so dass sie am Leben (shared_ptr) oder Risiko Abstürze zu halten, wenn sie verwendet werden, nachdem sie verschwunden sind (roh Zeiger). In Multi-Threaded-Anwendungen seine noch schlimmer - die die Objekte Sperren Sie zu verschiedenen Threads verteilen sind? Verstärkt geteilt und schwache Zeiger sind eine Komplexitätsfalle zu induzieren, wenn auf diese Weise verwendet, zumal sie gerade Thread-sicher ist genug unerfahrene Entwickler Bein zu stellen.

Wenn Sie einen Halter schaffen gehen, braucht es die Komplexität von Ihren Benutzern zu verstecken und entlasten sie von Belastungen Sie selbst verwalten können. Als Beispiel sendet eine Schnittstelle, bestehend aus a) Befehl an dem Subsystem (zB ein URI - / System / Untersystem / command param = value) und b) iterieren Subsystemen und Subsystems Befehle (über einen stl artigen Iterator) und gegebenenfalls c)? registrieren Subsystem Sie erlauben würde, fast alle Details der Implementierung von Ihren Benutzern zu verstecken, und erzwingen die Lebensdauer / Bestellung / Anforderungen intern verriegelt wird.

Ein iteratable / enumerable API ist in beträchtlichem Ausmaß bevorzugt Objekte in jedem Fall auszusetzen - die Befehle / Registrierungen leicht serialisiert zur Erzeugung von Testfällen oder Konfigurationsdateien werden könnten, und sie können interaktiv angezeigt werden (beispielsweise in einer Baumkontrolle, mit Dialogen zusammengesetzt, indem die verfügbaren Aktionen / Parameter) abfragt. Sie würden auch Ihre API-Nutzer von internen Veränderungen schützen Sie die Subsystemklassen vornehmen müssen können.

Ich möchte Sie warnen vor Anschluss an die Beratung in Aarons Antwort. Entwerfen eine Lösung für ein Problem, diesen einfach, die 5 verschiedene Entwurfsmuster erfordert nur zu implementieren, bedeutet, dass das falsche Problem gelöst wird. Ich bin auch müde, jeden, der Herr Myers im Zusammenhang zitiert zu entwerfen, da er selbst zugibt:

"Ich habe nicht die Produktion Software in mehr als 20 Jahren geschrieben, und ich habe nie Produktions-Software in C ++ geschrieben. Nein, nicht immer. Außerdem habe ich noch nie versucht, Produktions-Software in C ++ zu schreiben, so dass nicht nur ich bin keine wirklichen C ++ Entwickler, ich bin nicht einmal ein wannabe. Als Gegengewicht dies leicht die Tatsache ist, dass ich Forschungs-Software in C ++ Schulzeit während meiner Absolvent (1985-1993) geschrieben habe, aber selbst das war klein (ein paar tausend Zeilen) Single-Entwickler zu-weggeworfenen-schnell Sachen. und da vor als Berater über ein Dutzend Jahre streichend, meine C ++ Programmierung wurde Spielzeug beschränkt „mal sehen, wie das funktioniert“ (oder, manchmal, „mal sehen, wie viele Compiler diese Brüche“) Programme, in der Regel Programme, die eine einzelne Datei passen“.

Um nicht zu sagen, dass seine Bücher nicht wert sind, zu lesen, aber er hat keine Autorität auf die Gestaltung und die Komplexität zu sprechen.

In Ihrem Beispiel wäre es besser, wenn das System eine vector<Subsystem> gehalten, anstatt eine vector<shared_ptr<Subsystem> >. Sein sowohl einfacher und beseitigt die Sorge Sie haben. GetSubsystem würde stattdessen einen Verweis zurück.

Stapel Objekte werden in umgekehrter Reihenfolge freigegeben werden, zu dem, wo sie instanziiert, so dass, wenn der Entwickler die API versucht, den Smart-Pointer zu verwalten, ist es normalerweise kein Problem sein würde. Es gibt nur ein paar Dinge, die Sie gehen zu können, nicht verhindern, das Beste, was Sie tun können, ist während der Laufzeit zu warnen, vorzugsweise debug nur.

Ihr Beispiel für mich sehr ähnlich wie COM scheint, haben Sie Referenzzählung auf den Subsystemen unter Verwendung Shared_ptr zurückgegeben werden, aber Sie es auf dem System-Objekt selbst fehlen.

Wenn jedes der Subsystem-Objekte hat eine AddRef auf dem Systemobjekt auf Schöpfung und Release auf Zerstörung dest könnte man auf eine Ausnahme angezeigt, wenn der Referenzzähler nicht korrekt ist, wenn das Systemobjekt früh zerstört wird.

Die Verwendung von weak_ptr würde auch ermöglicht es Ihnen, eine Nachricht zu liefern, anstatt / aswell als Sprengung, wenn die Dinge in der falschen Reihenfolge befreit werden.

Das Wesen des Problems ist eine zirkuläre Referenz: System zum Subsystem bezieht und Subsystem, die wiederum bezieht sich auf System. Diese Art der Datenstruktur kann nicht einfach durch Referenzzählung behandelt werden - es richtigen Garbage Collection erfordert. Sie versuchen, die Schleife zu brechen, indem sie einen rohen Zeiger für eine der Kanten mit -. Dies wird nur mehr Komplikationen erzeugen

Mindestens zwei gute Lösungen vorgeschlagen worden, also werde ich versuchen, nicht die vorherigen Poster auszustechen. Ich kann nur beachten, dass in @ Aaron Lösung Sie einen Proxy für das System haben können, anstatt für Subsystemen -. Dependingo n was komplexer ist und was macht Sinn

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top