Was (nicht) zu tun in einem Konstruktor
Möchte ich Sie bitten, für Ihre besten Praktiken bezüglich der Konstruktoren in C++. Ich bin nicht ganz sicher, was ich tun sollte in einem Konstruktor und was nicht.
Soll ich verwenden Sie nur für Attribut-Initialisierungen, den Aufruf von parent Konstruktoren etc.?
Oder könnte ich sogar mehr komplexe Funktionen in Ihnen, wie das Lesen und Parsen der Konfiguration von Daten, einrichten von externen Bibliotheken ein.s.o.
Oder sollte ich schreiben, die spezielle Funktionen für diese? Bzw. init()
/cleanup()
?
Was sind die PRO 's und CON' s hier?
Dachte ich noch, dass ich zum Beispiel kann loszuwerden shared Pointer bei der Verwendung init()
und cleanup()
. Ich kann die Objekte auf dem Stapel, als Attribute der Klasse und initialisieren Sie es später es ist zwar bereits gebaut.
Wenn ich mit in den Konstruktor brauche ich zum instanziieren während der Laufzeit. Dann brauche ich einen Zeiger.
Ich weiß wirklich nicht, wie zu entscheiden ist.
Vielleicht können Sie mir helfen?
- gotw.ca/gotw/066.htm
- Ich denke, es ist eine schlechte Idee, um Dinge hinzufügen, wie
init()
undcleanup()
zu den Klassen. Das schreit nach Fehlern. Sie können nicht sicher sein, ob Sie eine Klasseinit()
ed. Man könnte hinzufügen, Funktionen zu prüfen, aber das macht es noch komplizierter. - Sorry, aber ich weiß nicht Recht bekommen es. Der Artikel, den Sie verlinkt Gespräche über Ausnahmen, soweit ich das verstanden habe.
- Ja tut es, aber verpassen Sie nicht den wichtigsten Punkt. Bereit sein, zu tun mit Konstruktor Ausfälle, die immer wartet, um zu geschehen.
- Tatsächlich ist es oft eine gute Idee, init() und cleanup () - Funktionen, aber Vorsicht, wenn init() virtuell ist es nicht Arbeit, die Sie anrufen aus einem Konstruktor. Wenn Sie factory-Objekte, obwohl Sie zwei-Stufen-Konstruktion, erstellen der Klasse, dann ruft init() auf. Klassen, die wrap-thread-Funktionen werden oft auf diese Weise getan
- Was meinst du mit "Attribute der Klasse"? Das ist kein C++ - Konzept, es sei denn, Sie verwenden einen nicht standardmäßigen Namen für etwas.
- Clark Kant: In der Tat, der Breite Konsens ist, dass die FQA ist eine Menge bullsht und wütend Schreien, gemischt mit ein paar Valide Kritikpunkte, sind komplett ausgeblasen Proportionen. Nicht eine gute C++ - Ressourcen. Insbesondere, nicht eine, die bietet *keine Lösungen oder Konstruktive Kritik. Zurück in den Tagen, die Sie verwendet, um zu bezeichnen das als "FAIL".
- manchmal müssen Sie neu initialisieren. (esp. auf embedded-Systemen)
- S: ja, Das ist richtig, aber dies sollte nicht der Standard für OO-Entwicklung. Dies führt zu ernsthaften Schwierigkeiten, wenn nicht sorgfältig verwendet.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Komplexe Logik und der Konstruktor nicht immer gut zu mischen, und es gibt starke Befürworter dagegen tun, die schwere Arbeit in einem Konstruktor (mit Begründung).
Die Grundregel ist, dass der Konstruktor darf Ertrag ein voll nutzbares Objekt.
Es bedeutet nicht, ein Objekt vollständig initialisiert ist, können Sie verzögern, einige Initialisierung (denken Sie faul), solange der Benutzer nicht haben, um darüber nachzudenken.
Wenn es schwer ist Arbeit zu tun, könnten Sie sich entscheiden, gehen Sie mit einem builder-Methode, wird die schwere Arbeit, die vor Aufruf des Konstruktors. Zum Beispiel, sich vorstellen, abrufen von Einstellungen aus einer Datenbank und baut eine Einstellung-Objekt.
Diese builder-Methode ist nützlich, wenn die Verschiebung der Bau des Objekts ergibt sich ein entscheidender Vorteil. Zum Beispiel, wenn die Objekte greifen eine Menge Speicher, die Verschiebung der Speicher-Erfassung nach Aufgaben, die Sie wahrscheinlich zu scheitern, ist vielleicht gar keine schlechte Idee.
Diese builder-Methode impliziert Privaten Konstruktor und Öffentlichen (oder Freund) - generator. Beachten Sie, dass mit einem Privaten Konstruktor legt eine Reihe von Einschränkungen in Bezug auf die Verwendungen, die gemacht werden können von einer Klasse (kann nicht gespeichert werden in STL-Containern, zum Beispiel), so müssen Sie möglicherweise zu verschmelzen, in anderen mustern. Das ist der Grund, warum diese Methode sollte nur noch in Ausnahmefällen angewandt werden.
Möchten Sie vielleicht zu prüfen, wie um zu testen, wie Personen auch, wenn Sie angewiesen sind auf eine externe Sache (Datei /DB), denke über Dependency Injection, es hilft wirklich mit Unit-Tests.
Den häufigste Fehler zu tun in einem Konstruktor als auch ein Destruktor ist die Verwendung von Polymorphismus. Polymorphismus funktioniert oft nicht in Konstruktoren !
z.B.:
dies ist, weil das Objekt B ist noch nicht gebaut, während der Durchführung der Konstruktor der Mutter-Klasse Ein... also unmöglich für Sie zum Aufruf der überschriebenen version der
void doA();
Ein Beispiel, bei dem Polymorphismus wird Arbeit im Konstruktor:
Dieser Zeit, der Grund ist: in der Zeit der
virtual
FunktiondoOverridenBehaviour()
aufgerufen wird, das Objekt b bereits initialisiert ist (aber noch nicht gebaut), bedeutet dies, dass der virtuelle Tabelle initialisiert wird, und somit kann durchführen Polymorphismus.class B : public A
?delete this
oder Destruktor im Konstruktor.delete this
im Konstruktor" — hast du nicht gehört von Resource Acquisition Is Abwertung?init()
", obwohl. Im wesentlichen argumentiert gegen jede factoring-code in Konstruktoren.init
sollten nicht als Ersatz für die member-Initialisierungsliste, aber einer von Stroustrup ' s Lieblings-C++0x-features, nicht-statische member-Initialisierungen, bekommt das beste aus beiden Welten.Doing more complex things in constructors is okay
: Das ist falsch.Einfache Antwort: es hängt davon ab.
Beim Entwurf der software, die Sie sollten vielleicht möchten Sie das Programm über die RAII - Prinzip ("Resource Acquisition is initialization"). Dies bedeutet (unter anderem), dass das Objekt selbst ist zuständig für Ihre Ressourcen, und nicht der Anrufer. Auch, Sie wollen vielleicht machen Sie sich mit Ausnahme Sicherheit (in unterschiedlichen Graden).
Betrachten Sie zum Beispiel:
Wenn Euch das design der Klasse
MyFile
in der Weise, dass Sie sicher sein können, bevordoSomething(f)
dassf
initialisiert ist, ist Sie sparen eine Menge ärger-Kontrolle für, die. Auch, wenn Sie die Freigabe der Ressourcen statt, die vonf
im Destruktor, D. H. schließen Sie die Datei verarbeiten, sind Sie auf der sicheren Seite und es ist einfach zu bedienen.In diesem speziellen Fall können Sie die speziellen Eigenschaften von Konstruktoren:
virtual
Methoden, sollten Sie nicht, rufen Sie die aus dem inneren der Konstruktor, es sei denn, Sie wissen, was Sie tun-Sie (oder höher) bekommen vielleicht Wundern, warum die virtuelle überschreiben-Methode nicht aufgerufen wird. Am besten nicht zu verwirren niemanden.Einen Konstruktor müssen lassen Sie Ihr Objekt in ein verwendbar Stand. Und weil es immer klug, um zu die es schwierig machen, verwenden Sie Ihren API-falsch, das beste, was zu tun ist make es leicht verwendet werden, richtig (sic zu Scott Meyers). Mache die Initialisierung innerhalb des Konstruktors sollte Ihr Standard-Strategie-aber es gibt immer Ausnahmen, natürlich.
So: Es ist ein guter Weg, um Konstruktoren für die Initialisierung. Es ist nicht immer möglich, z.B. GUI-frameworks oft müssen gebaut werden, dann initialisiert. Aber wenn du das design der software völlig in diese Richtung, können Sie sparen eine Menge ärger.
Vom Die Programmiersprache C++:
Ich in der Regel betrachten Sie die folgende Regel, wenn Sie eine Klasse entwerfen, die: ich muss in der Lage sein, um verwenden Sie eine Methode, der Klasse sicher nachdem der Konstruktor ausgeführt wurde. Sicher bedeutet hier, dass Sie könnte immer Ausnahmen, wenn das Objekt
init()
- Methode wurde nicht aufgerufen, aber ich ziehe es vor etwas zu haben, was tatsächlich nutzbar ist.Beispielsweise
class std::string
könnte nicht keinen Speicher zuweisen, wenn Sie den Standard-Konstruktor, da die meisten Methoden (z.B.begin()
undend()
) korrekt funktionieren würde, wenn beide Rückgabewert null-Zeiger, undc_str()
nicht unbedingt die Rückkehr der aktuelle Puffer für die anderen design-Gründen, deshalb muss er vorbereitet werden, um Speicher zu jeder Zeit. Nicht allokierung von Speicher, in diesem Fall führt noch auf eine brauchbare string-Instanz.Umgekehrt, Verwendung von RAII im Gültigkeitsbereich Wachen für mutex-locks ist ein Beispiel für einen Konstruktor ausführen können für eine beliebig lange Zeit (bis der lock-Eigentümer gibt es) ist doch gemeinhin als gute Praxis.
In jedem Fall, lazy-Initialisierung kann getan werden, sicherere Wege als über eine
init()
Methode. Eine Möglichkeit ist die Nutzung von einigen Zwischenklasse erfasst alle Parameter an den Konstruktor. Ein anderes ist die Verwendung des builder pattern.Ist ein Konstruktor erwartet ein Objekt, das verwendet werden kann, aus dem Wort gehen. Wenn Sie aus irgendeinem Grund nicht in der Lage ist, um einen brauchbaren Objekt, es sollte eine exception zu werfen und mit ihm getan werden. So, aller ergänzenden Methoden/Funktionen, die erforderlich sind für ein Objekt richtig funktioniert, sollte der Aufruf von Konstruktor (es sei denn, Sie wollen, um lazy loading-wie Funktionen)
Ich würde eher Fragen:
und alles, was oben nicht aufgeführt ist die Antwort auf die OP ' s Frage.
Ich denke, dass der einzige Zweck der Konstruktor ist
initialisiert alle Membervariablen in einem bekannten Zustand, und
Zuweisung von Ressourcen (falls zutreffend).
Den Artikel #1 klingt so einfach, aber ich sehen, dass vergessen/ignoriert, auf regelmäßiger basis und nur daran erinnert zu werden, durch eine statische Analyse-tool. Unterschätzen Sie nie diese (Wortspiel beabsichtigt).
KÖNNEN Sie werfen aus einem Konstruktor, und es ist oft die bessere option als die Schaffung eines zombie-Objekt, d.h. ein Objekt hat einen Status "fehlgeschlagen".
Sollten Sie allerdings nie werfen Sie aus einem Destruktor.
Der compiler wissen, was die Reihenfolge der member-Objekte aufgebaut sind - die Reihenfolge, wie Sie in der Kopfzeile angezeigt. Der Destruktor wird jedoch nicht aufgerufen werden, wie Sie gesagt haben, was bedeutet, wenn Sie anrufen, neuen mehrere Male innerhalb eines Konstruktors verlassen Sie sich nicht auf Ihrem Destruktor aufrufen der löscht für Sie. Wenn du Sie in der smart-pointer-Objekte, das ist kein problem, da diese Objekte werden gelöscht. Wenn Sie wollen, dass Sie als rohe Zeiger dann legen Sie Sie vorübergehend in einen auto_ptr-Objekte, bis Sie wissen, dass Ihr Konstruktor nicht mehr werfen, dann Aufruf von release() für alle Ihre auto_ptrs.
Wird ein Konstruktor verwendet, um zu konstruieren ein Objekt, nicht mehr und nicht weniger. Sie müssen tun, was es braucht, zu schaffen, die die Klasse von Invarianten in einem Konstruktor, und wie Komplex ist es wirklich hängt von der Art des Objekts initialisiert werden.
Separate init() Funktionen sind eine gute Idee, nur wenn aus irgendeinem Grund Sie nicht verwenden können, Ausnahmen.
Gut, «constructor» kommt aus dem Bau, bauen, einrichten. So es ist, wo alle die Initialisierung passiert. Wenn Sie die Instanz einer Klasse, den Konstruktor verwenden, um sicherzustellen, dass alles getan wird, um das neue Objekt zu verarbeiten, mit.
Ich glaube, das wichtigste ist der gesunde Menschenverstand! Es gibt viel Gerede über die do 's und dont' s - alles schön und gut, aber der Schlüssel ist zu prüfen, wie Sie Ihr Objekt verwendet werden. Zum Beispiel,
Wenn dieses Objekt eine einzelne Instanz, die konstruiert wird, an den start und die Konstruktion ist nicht in der kritische-Pfad - warum nicht die schwere Arbeit in den Konstruktor (solange Sie Ausnahmen entsprechend, sind es vielleicht sogar mehr Sinn machen)? Wenn auf der anderen Seite, es ist ein ganz leichtes Objekt, das erstellt und zerstört in einer Schleife für eine kurze Zeit versuchen, so wenig wie möglich (abgesehen von der Initialisierung der Mitglieder, zum Beispiel) (funktoren sind ein sehr gutes Beispiel dafür...)
Gibt es Vorteile, wenn ein zwei-Phasen-laden (oder was auch immer), aber der größte Nachteil ist vergessen, es zu nennen - wie viele von uns haben das getan?? 🙂
So, meine tuppence ist, nicht bleiben, um eine harte und schnelle Regel, genau hinschauen, wie Sie Ihr Objekt verwendet werden soll, und dann entwerfen Sie entsprechend!
Können Sie tun, was Sie wollen, aber verwenden Konstruktor für diesen Zweck für was es call - Objekt erstellen.
und wenn für das aufrufen müssen andere Methoden, das ist Ok.
Folgen Sie einfach nur eine Regel - Mach es nicht komplexe mehr, als es braucht.
Gute Praxis ist zu tun Konstruktor so einfach wie möglich, aber das nicht bedeutet, dass Sie brauchen nur initialisieren Mitglieder.
Idealerweise sollten Sie keinen code in Ihrem constuctors, der jemals (abgesehen von Attribut-Belegung). Es gibt einen wichtigen Grund: Es verhindert, dass die Komposition von Objekten und macht Sie un-erweiterbar.
Hier ist mein blog-post dazu: Konstruktoren Werden Muss, Code-Free