Frage

Was ist eine gute bestehende Klasse / Design-Muster für mehrstufige Konstruktion / Initialisierung eines Objekts in C ++?

Ich habe eine Klasse mit einigen Datenelementen, die in verschiedenen Punkten in dem Programmfluss initialisiert werden sollen, so dass ihre Initialisierung verzögert werden muss. Zum Beispiel eines Argument kann aus einer Datei und eine andere aus dem Netzwerk gelesen werden.

Zur Zeit verwende ich boost :: optional für den verzögerten Aufbau der Datenelemente, aber es stört mich, dass optional ist semantisch anders als verzögerungs aufgebaut.

Was ich brauche, erinnert Merkmale von boost :: bind und Lambda-Teilfunktion Anwendung und die Verwendung dieser Bibliotheken, die ich wahrscheinlich mehrstufigen Aufbau gestalten können - aber ich ziehe mit bestehenden, getestet Klassen. (Oder vielleicht gibt es einen anderen mehrstufigen Konstruktion Muster, das ich nicht kenne).

War es hilfreich?

Lösung

Die zentrale Frage ist, ob Sie vollständig besiedelte Objekte aus unvollständig bevölkerten Objekten unterscheiden sollten auf der Typebene . Wenn Sie sich entscheiden, keinen Unterschied machen, dann benutzen Sie einfach boost::optional oder ähnlich wie Sie tun: Das macht es einfach, schnell zu bekommen Codierung. OTOH Sie können nicht den Compiler bekommen die Anforderung zu erzwingen, dass eine bestimmte Funktion ein vollständig bevölkerten Objekt erfordert; Sie müssen zur Laufzeit ausführen von Feldern jedes Mal zu überprüfen.

Parameter-Gruppentypen

Wenn Sie vollständig bevölkerten Objekte aus unvollständig besiedelten Objekten auf der Typebene unterscheiden können, können Sie die Anforderung erzwingen, dass eine Funktion ein komplettes Objekt übergeben werden. Um dies zu tun würde ich vorschlagen, einen entsprechenden Typ XParams für jeden relevanten Typ X zu schaffen. XParams hat boost::optional Mitglieder und Setter Funktionen für jeden Parameter, der nach dem anfänglichen Aufbau eingestellt werden kann. Dann können Sie X zwingen, nur eine (nicht-Kopie) Konstruktor zu haben, die eine XParams als einziges Argument und prüft, nimmt, dass jede notwendige Parametersatz innerhalb dieses XParams Objekt gewesen ist. (Nicht sicher, ob dieses Muster einen Namen hat - jemand wie zu bearbeiten diese uns in füllen)

"Teilobjekt" Typen

Das funktioniert wunderbar, wenn Sie nicht wirklich müssen tun etwas mit dem Objekt, bevor es vollständig ausgefüllt ist (vielleicht auch andere als trivial Sachen wie die Feldwerte zurück). Wenn Sie müssen manchmal einen unvollständig besiedelten X wie ein „voll“ X behandeln, können Sie stattdessen X ergeben sich aus einer Art XPartial machen, die alle die Logik enthält, sowie protected virtuelle Methoden zur Voraussetzung Durchführung von Tests, dass Test, ob alle notwendigen Felder sind besiedelt. Dann, wenn X sorgt dafür, dass es immer nur in einem vollständig besiedelten Staat aufgebaut werden, steht die geschützten Methoden mit trivialen Kontrollen außer Kraft setzen, dass immer true zurück:

class XPartial {
    optional<string> name_;

public:
    void setName(string x) { name_.reset(x); }  // Can add getters and/or ctors
    string makeGreeting(string title) {
        if (checkMakeGreeting_()) {             // Is it safe?
            return string("Hello, ") + title + " " + *name_;
        } else {
            throw domain_error("ZOINKS");       // Or similar
        }
    }
    bool isComplete() const { return checkMakeGreeting_(); }  // All tests here

protected:
    virtual bool checkMakeGreeting_() const { return name_; }   // Populated?
};

class X : public XPartial {
    X();     // Forbid default-construction; or, you could supply a "full" ctor

public:
    explicit X(XPartial const& x) : XPartial(x) {  // Avoid implicit conversion
        if (!x.isComplete()) throw domain_error("ZOINKS");
    }

    X& operator=(XPartial const& x) {
        if (!x.isComplete()) throw domain_error("ZOINKS");
        return static_cast<X&>(XPartial::operator=(x));
    }

protected:
    virtual bool checkMakeGreeting_() { return true; }   // No checking needed!
};

Auch wenn es die Vererbung scheint hier „hinten nach vorne“, es auf diese Weise Mittel zu tun, dass ein X sicher überall geliefert werden kann eine XPartial& wird gefragt, so dass dieser Ansatz folgt die Liskov Substitutionsprinzip . Dies bedeutet, dass eine Funktion einen Parametertyp X&, um anzuzeigen, verwenden kann, braucht es ein komplettes X Objekt oder XPartial&, um anzuzeigen, es teilweise besiedelte Objekte verarbeiten kann -. In diesem Fall entweder ein XPartial Objekt oder eine vollständige X geführt werden kann

Ursprünglich hatte ich isComplete() als protected, fand aber das hat nicht funktioniert, da X Kopie Ctor und Zuweisungsoperator diese Funktion auf ihrem XPartial& Argumente nennen müssen, und sie haben keinen ausreichenden Zugang. Nach einigem Nachdenken, macht es mehr Sinn, öffentlich diese Funktionalität aus.

Andere Tipps

Ich muss hier etwas fehlen - ich tun, um diese Art der Sache, die ganze Zeit. Es ist sehr üblich, Objekte zu haben, die sind groß und / oder nicht durch eine Klasse unter allen Umständen erforderlich. So schaffen sie dynamisch!

struct Big {
    char a[1000000];
};

class A {
  public: 
    A() : big(0) {}
   ~A() { delete big; }

   void f() {
      makebig();
      big->a[42] = 66;
   }
  private:
    Big * big;
    void makebig() {
      if ( ! big ) {
         big = new Big;
      }
    }
};

Ich sehe nicht die Notwendigkeit, etwas schicker als das, außer dass makebig () soll wahrscheinlich const (und vielleicht auch inline), und die Big Zeiger sollte wahrscheinlich wandelbar sein. Und natürlich A muss in der Lage sein, große zu konstruieren, die in anderen Fällen kann bedeuten, das Caching Konstruktor Parameter der enthaltenen Klasse. Sie werden auch auf einer Kopier- / Zuweisungsrichtlinie entscheiden müssen -. Ich wohl beide für diese Art von Klasse verbieten würde

Ich weiß nicht, von irgendwelchen Mustern mit diesem speziellen Thema zu befassen. Es ist eine heikle Design Frage und eine etwas einzigartig zu Sprachen wie C ++. Ein weiteres Problem ist, dass die Antwort auf diese Frage eng an Ihren individuellen (oder Unternehmen) Codierstil gebunden ist.

würde ich Zeiger für diese Elemente verwenden, und wenn sie gebaut werden müssen, weisen sie zugleich. Sie können diese verwenden, auto_ptr und überprüfen gegen NULL, um zu sehen, wenn sie initialisiert werden. (Ich denke, von Zeigern eine eingebaute „optional“ Typ in C / C ++ / Java sind, gibt es andere Sprachen, in denen NULL kein gültiger Zeiger ist).

Ein Thema als Frage des Stils ist, dass Sie auf Ihre Konstrukteure angewiesen sein kann zu viel Arbeit zu tun. Wenn ich OO bin Codierung, habe ich die Konstrukteure gerade genug, um Arbeit zu tun, das Objekt in einem konsistenten Zustand zu erhalten. Zum Beispiel, wenn ich eine Image Klasse haben und ich möchte aus einer Datei lesen, konnte ich dies tun:

image = new Image("unicorn.jpeg"); /* I'm not fond of this style */

oder, könnte ich dies tun:

image = new Image(); /* I like this better */
image->read("unicorn.jpeg");

Es kann schwierig werden zu Grunde darüber, wie ein C ++ Programm funktioniert, wenn die Konstrukteure in ihnen eine Menge Code, vor allem wenn man die Frage stellen: „Was passiert, wenn ein Konstruktor ausfällt?“ Dies ist der Hauptvorteil von Code aus der Konstrukteurs bewegen.

Ich würde mehr zu sagen haben, aber ich weiß nicht, was Sie versuchen, mit einer verzögerten Aufbau zu tun.

Edit: Ich erinnerte mich, dass es eine (etwas perverse) Art und Weise einen Konstruktor auf einem Objekt zu einem beliebigen Zeitpunkt zu nennen. Hier ein Beispiel:

class Counter {
public:
    Counter(int &cref) : c(cref) { }
    void incr(int x) { c += x; }
private:
    int &c;
};

void dontTryThisAtHome() {
    int i = 0, j = 0;
    Counter c(i);       // Call constructor first time on c
    c.incr(5);          // now i = 5
    new(&c) Counter(j); // Call the constructor AGAIN on c
    c.incr(3);          // now j = 3
}

Beachten Sie, dass, da dies etwas so rücksichtslos tun könnten Sie die Verachtung Ihrer Kollegen Programmierer verdienen, es sei denn, Sie für die Verwendung dieser Technik gute Gründe haben. Dies gilt auch den Konstruktor nicht verzögern, nur können Sie es nennen Sie es später erneut.

Mit boost.optional sieht aus wie eine gute Lösung für einige Anwendungsfälle. Ich habe nicht viel mit ihm gespielt, damit ich nicht viel kommentieren. Eine Sache, die ich im Auge behalten, wenn es um solche Funktionalität zu tun ist, ob ich statt Standardladenen Konstruktoren verwenden kann und Kopierkonstruktoren.

Wenn ich eine solche Funktionalität benötigen würde ich nur einen Zeiger auf den Typ des erforderlichen Feld wie folgt verwendet werden:

public:
  MyClass() : field_(0) { } // constructor, additional initializers and code omitted
  ~MyClass() {
    if (field_)
      delete field_; // free the constructed object only if initialized
  }
  ...
private:
  ...
  field_type* field_;

Als nächstes statt unter Verwendung des Zeigers würde ich das Feld durch die folgende Methode aufrufen:

private:
  ...
  field_type& field() {
    if (!field_)
      field_ = new field_type(...);
    return field_;
  }

Ich habe const-Zugriffssemantik weggelassen

Der einfachste Weg, ich weiß, ist ähnlich der Technik von Dietrich Epp vorgeschlagen, außer es Ihnen erlaubt, wirklich den Bau eines Objekts zu verzögern, bis ein Moment Ihrer Wahl.

Grundsätzlich gilt:. Behält sich das Objekt mit malloc anstelle von neuen (und dadurch den Konstruktor Umgehung), dann rufen Sie die überladene neuen Operator, wenn Sie wirklich wollen neu das Objekt über die Platzierung konstruieren

Beispiel:

Object *x = (Object *) malloc(sizeof(Object));
//Use the object member items here. Be careful: no constructors have been called!
//This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!

//Now we want to call the constructor of the object
new(x) Object(params);

//However, you must remember to also manually call the destructor!
x.~Object();
free(x);

//Note: if you're the malloc and new calls in your development stack 
//store in the same heap, you can just call delete(x) instead of the 
//destructor followed by free, but the above is the  correct way of 
//doing it

persönlich das einzige Mal, dass ich diese Syntax verwendet habe, war, als ich hatte eine benutzerdefinierte C-basierte Allocator für C ++ Objekte zu verwenden. Als Dietrich schlägt vor, sollten Sie fragen, ob Sie wirklich, wirklich die Konstruktoraufruf verzögern müssen. Die Basis Konstruktor sollte das Nötigste durchführen, um das Objekt in einen brauchbaren Zustand zu erhalten, während andere ladenen Konstruktoren mehr Arbeit durchführen können je nach Bedarf.

Ich weiß nicht, ob es ein formelles Muster ist für diesen. In Orten, wo ich es gesehen habe, nannten wir es „faul“, „Nachfrage“ oder „on demand“.

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