init Funktion oder Konstruktor
-
Dein Ansatz führt zum inflationären Gebrauch von Exceptions. Jede Klasse, deren
Instanzen Function-Calls machen müssen, um benutzbar zu sein, muss dann
Exceptions werfen. Das ist meiner Meinung nach sicher nicht der Sinn von
Exceptions. Dafür hat Gott return-values geschaffen.Ein weiteres Argument für init() Methoden ist, dass der this-Pointer erst gültig
ist, wenn der Konstruktor verlassen wurde. In nem Konstruktor sollte man nie
einen Function-Call mit "this" als Parameter machen.In ner init() Methode geht das aber sehr wohl.
-
Hans_Guck_In_Die_Luft schrieb:
Dein Ansatz führt zum inflationären Gebrauch von Exceptions. Jede Klasse, deren
Instanzen Function-Calls machen müssen, um benutzbar zu sein, muss dann
Exceptions werfen. Das ist meiner Meinung nach sicher nicht der Sinn von
Exceptions.Warum?
Hans_Guck_In_Die_Luft schrieb:
Ein weiteres Argument für init() Methoden ist, dass der this-Pointer erst gültig
ist, wenn der Konstruktor verlassen wurde. In nem Konstruktor sollte man nie
einen Function-Call mit "this" als Parameter machen.Unfug.
Hans_Guck_In_Die_Luft schrieb:
In ner init() Methode geht das aber sehr wohl.
Nur dass du this dann immer noch auf ein nicht richtig initialisiertes Objekt zeigt und sich die aufgerufene Funktion nicht auf die Einhaltung der Objektinvarianzen verlassen kann. Ich sehe hier keinen Unterschied zum Aufruf aus Konstruktoren heraus.
-
Hans_Guck_In_Die_Luft schrieb:
Ein weiteres Argument für init() Methoden ist, dass der this-Pointer erst gültig
ist, wenn der Konstruktor verlassen wurde. In nem Konstruktor sollte man nie
einen Function-Call mit "this" als Parameter machen.Camper hats bereits gesagt, dass das Unfug ist. Wenn das stimmen würde, würde das heissen, dass man auch keine Memberfunktionen aus dem Konstruktor aufrufen darf und dann wäre so ziemlich die komplette Standardbibliothek falsch oder was auch immer du meinst das das ist..
Es bestehen gefahren, wie wenn du z.B über den this Zeiger (natürlich auch sonst) auf ein Objekt zugreifst, dass noch nicht initialisiert wurde, daher ist der Gebrauch des this Zeigers micht Vorsicht zu geniesen ( vor allem in der Initialisierungsliste), abre das rührt hauptsächlich daher, dass das Objekt, dass den Zeiger benutzt ev. meint, dass das Objekt bereits besteht. Aber den Zeiger einfach speichern ist kein Problem.
Ein zweites Problem könnte sein, dass der this Zeiger gebraucht werden könnte, um (auch wieder unvorsichtigerweise) einen polymorphen Funktionsaufruf zu machen und das kann ebenfalls in die Hose gehen. (wird es auch).Der Konstruktor ist dazu da, um die Invarianz zu erstellen und ggf. um ein paar Parameter zu setzen und Berechnungen zu machen, welche sonst über Memberfunktionen gleich nach der Erstelltung aufgerufen werde müssten. (also rein syntax sugar).
-
Ok, die Wahrheit liegt in der Mitte:
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.7
this im C'tor benutzen geht manchmal. Manchmal isses gefährlich. Meiner Meinung kann man einfach drauf verzichten. Habe noch nie Code von guten Leuten gesehen, die das machen.
Dein Ansatz führt zum inflationären Gebrauch von Exceptions. Jede Klasse, deren
Instanzen Function-Calls machen müssen, um benutzbar zu sein, muss dann
Exceptions werfen. Das ist meiner Meinung nach sicher nicht der Sinn von
Exceptions.
Warum?Exceptions sind Ausnahmen. Willste jedes mal, wenn ein Pointer, der NULL ist,
jedoch nicht NULL sein darf, ne Exception werfen? Exceptions sind meiner
Meinung nach für Sachen, falls man kein File-Handle bekommt, die Socket-
Connection nicht funzt oder der Drucker nicht antwortet. Also für Sachen, deren
Ursache außerhalb der eigentlichen Applikation liegt. Für interene "Fehler" wie
z.B. Mutex ist blockiert oder eigene Invarianten sind verletzt, kann man einfach Rückgabewerte verwenden.
-
Hans_Guck_In_Die_Luft schrieb:
Willste jedes mal, wenn ein Pointer, der NULL ist, jedoch nicht NULL sein darf, ne Exception werfen?
Wieso erlaubst du dann überhaupt erst die Übergabe von Zeigern? Dann kannst du auch eine Referenz verlangen. Und schon musst du diese Überprüfung nicht mehr machen.
Finde es aber sehr interessant, dass du dies als Argument bringst. Lässt mich auch sehr vermuten, dass du viel zu stark in C denkst.Es ist zudem auch die Aufgabe des Programmierers, richtige Werte zu prüfen, welche an den Konstruktor übergeben werden. Wenn dann trotzdem falsche Werte übergeben wurden, dann ist eine Exception gerechtfertigt. Womöglich sogar ein
assert
.Wenn die Konstruktion eines Objektes schief läuft, dann ist wirklich etwas sehr schief gelaufen, was eine Exception völlig rechtfertigt.
Grüssli
-
Hans_Guck_In_Die_Luft schrieb:
Ok, die Wahrheit liegt in der Mitte:
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.7
this im C'tor benutzen geht manchmal. Manchmal isses gefährlich. Meiner Meinung kann man einfach drauf verzichten. Habe noch nie Code von guten Leuten gesehen, die das machen.
Das ist ja genau das, was ich geschrieben habe.
Ob man this braucht oder nicht kommt halt drauf an. Implizit brauchen den sehr viele. Explizit eher selten. Ich brauche die explizite Variante eigentlich nur an einem Ort, wo sich ein Objekt selbst bei einem Singleton registriert und dann im Destruktor wieder abmeldet. Da finde ich das noch ganz praktisch this im Konstruktor zu benutzen.Exceptions sind Ausnahmen. Willste jedes mal, wenn ein Pointer, der NULL ist,
jedoch nicht NULL sein darf, ne Exception werfen?Wenn irgendwo, wo ein Zeiger nicht 0 sein darf 0 ist, dann ja. Wenn du da mal in üblichen Code schaust, dann wirst du sehen, dass da ein assert ist. Ich würde eine Version bevorzugen, die eine Exception wirft. Dann kann man da nämlich ein paar Dinge auch noch im Release Mode stehen lassen und kriegt allenfalls eine gute Meldung und nicht einfach nur einen Absturz.
Ich stelle mal (wieder) die Brück zu Eiffel, wo Invarianten und Preconditions (und auch Postconditions) tief in der Sprache verwurzelt sind.
Wenn eine Invariante oder aber auch Precondition nicht eingehalten wird, dann sollte das dem Programmierer an den Kopf springen so, dass er das nicht ignorieren kann. Und da sind Exceptions einge gute Möglichkeit.
-
Ich bin der Meinung, dass die Verwendung von Exceptions um Programmierfehler zu
finden eine Vergewaltigung des Exception-Konzeptes ist. ASSERT's sind da das
Mittel der Wahl. Letzendlich gibt es hier aber kein "richtig" und kein "falsch".Fehlerbehandlung ist einfach eine Designfrage. Hauptsache man macht es konsequent
und fängt nicht an hier mal Exceptions zu werfen, dann mit Message-Box-Debug-
Ausgaben zu arbeiten, in der nächsten Klasse 200 Asserts zu verwenden und in ner
anderen Klasse 30 Fehlercodes mit #define einzuführen.Am Ende des Tages läuft es auf die Frage hinaus:
"Wer ist verantwortlich für den Zustand eines Objektes? Der Caller oder der
Callee?" Da es wegen einiger C++ Sprachfeautures (Konstruktoren-Konzept,
this-Pointer-Problematik) oft problematisch ist, dass ein Objekt selbst dafür
sorgt, dass es korrekt initialisiert ist, hat sich der Ansatz bewährt, dass der
Caller guckt, ob er ein sinnvoll initialisiertes Objekt vorfindet. Und dafür sind
init() Methoden eine unkomplizierte Sache. Warum ein init() Aufruf so verpöhnt zu
sein scheint, ist mir nicht ganz klar.
-
Hans_Guck_In_Die_Luft schrieb:
Fehlerbehandlung ist einfach eine Designfrage.
Jein, es hängt auch stark mit den Möglichkeiten der Sprache zusammen. Eine C Fehlerbehandlung in C++ zu machen, halte ich einfach nur für verkehrt.
Hans_Guck_In_Die_Luft schrieb:
"Wer ist verantwortlich für den Zustand eines Objektes? Der Caller oder der Callee?" Da es wegen einiger C++ Sprachfeautures (Konstruktoren-Konzept, this-Pointer-Problematik) oft problematisch ist, dass ein Objekt selbst dafür sorgt, dass es korrekt initialisiert ist, hat sich der Ansatz bewährt, dass der Caller guckt, ob er ein sinnvoll initialisiertes Objekt vorfindet.
Aber das ist absoluter QUATSCH. In C++ hat sich dieser Ansatz überhaupt nicht bewährt. Modernes C++ verwendet keine
init
Methoden, sondern direkt den Konstruktor. Es gibt gar keinen Grund für eineinit
Methode. Sowas ist nur in C von nöten, da man dort keine Konstruktoren hat. Und da man keine Exceptions in C hat, verwendet man dort auch den Rückgabewert der Funktion.Du solltest dich vielleicht ein wenig mehr mit den Konzepten in C++ auseinander setzen. Mal rein zum starten, würde ich diese 3 Artikel empfehlen:
Exception-Handling
Modernes Exception-Handling: Die Grundlagen
Modernes Exception-Handling: Hinter den KulissenVor allem die letzten beiden Artikel, dürften für dich interessant sein.
Grüssli
-
Hans_Guck_In_Die_Luft schrieb:
Da es wegen einiger C++ Sprachfeautures (Konstruktoren-Konzept,
this-Pointer-Problematik) oft problematisch ist, dass ein Objekt selbst dafür
sorgt, dass es korrekt initialisiert ist, hat sich der Ansatz bewährt, dass der
Caller guckt, ob er ein sinnvoll initialisiertes Objekt vorfindet. Und dafür sind
init() Methoden eine unkomplizierte Sache.Das stimmt überhaupt nicht. Mit Exceptions hat man im Gegensatz zu
Init()
-Methoden mitbool
-Rückgabewerten die Möglichkeit, bereits die Konstruktion eines Objektes abzubrechen. So kann man verhindern, dass Zombie-Objekte entstehen, die einen undefinierten Status haben. Es gibt natürlich Ausnahmen, in denen eine verzögerte Initialisierung sinnvoll ist. Aber im Allgemeinen hat ein Konstruktor nur Vorteile.Was genau ist eigentlich am "Konstruktoren-Konzept" von C++ problematisch?
Hans_Guck_In_Die_Luft schrieb:
Warum ein init() Aufruf so verpöhnt zu
sein scheint, ist mir nicht ganz klar.Weil er moderne, objektorientierte Konzepte wie Klasseninvarianzen nicht berücksichtigt und den Programmierer zu C-ähnlichem Stil zwingt, wo manuell initialisiert (und freigegeben) werden muss. Benutzt du auch öffentliche
Cleanup()
-Methoden statt Destruktoren? Konsequent wäre es zumindest.
-
Hans_Guck_In_Die_Luft schrieb:
Da es wegen einiger C++ Sprachfeautures (Konstruktoren-Konzept,
this-Pointer-Problematik) oft problematisch ist, dass ein Objekt selbst dafür
sorgt, dass es korrekt initialisiert ist, hat sich der Ansatz bewährt, dass der
Caller guckt, ob er ein sinnvoll initialisiertes Objekt vorfindet.Was für ein bildhaft schöner Schwachsinn!
Du hast gerade RAII "rückwärts-erfunden"
Leute gibts...
Es gibt keine "this-Pointer-Problematik", und es ist auch nicht problematisch, ein Objekt selbst dafür verantwortlich zu machen, dass es korrekt initialisert ist. Im Gegenteil: das ist die einzige Art wie man sinnvoll C++ programmieren kann.
-
hustbaer schrieb:
Du hast gerade RAII "rückwärts-erfunden"
Ich denke das triffts recht genau.
-
Das stimmt überhaupt nicht. Mit Exceptions hat man im Gegensatz zu Init()-Methoden mit bool-Rückgabewerten die Möglichkeit, bereits die Konstruktion eines Objektes abzubrechen. So kann man verhindern, dass Zombie-Objekte entstehen, die einen undefinierten Status haben. Es gibt natürlich Ausnahmen, in denen eine verzögerte Initialisierung sinnvoll ist. Aber im Allgemeinen hat ein Konstruktor nur Vorteile.
Hier mal ein Gegenbeispiel. Der Konstruktor alloziert Speicher. Danach kommt ne Exception geflogen, weil die Datei nicht geöffnet werden kann. Der Destruktor wird nie aufgerufen und man hat nen fetten Memory Leak.
class CMyObject { double * m_pArray; std::fstream m_ofs; //... }; CMyObject::CMyObject() { m_pArray = new double[100]; m_ofs.open("C:\test.txt"); if (!m_ofs) throw std::exception("(ofstream) error while opening file."); } CMyObject::~CMyObject() { if(m_pArray) { delete m_pArray; m_pArray = NULL; } m_ofs.close(); }
-
deshalb sorgt man ja auch dafür, dass das im ctor nicht geschieht... und wenn doch ist man - logischerweise - selbst dafür verantwortlich... dann muss man halt noch ein try im ctor um den code packen und sich im catch selbst darum kümmern, dass alles wieder freigegeben wird, was man dynamisch angefordert hat - und schmeißt die exception dann halt einfach weiter...
bb
-
Blödes Beispiel.
class my_object { std::vector<double> array; std::ofstream ofs; public: my_object() : array(100), ofs("test.txt") { if(!fs) throw std::exception("(ofstream) error while opening file."); } };
Mehr brauchts nicht.
-
Hans_Guck_In_Die_Luft schrieb:
Hier mal ein Gegenbeispiel. Der Konstruktor alloziert Speicher. Danach kommt ne Exception geflogen, weil die Datei nicht geöffnet werden kann. Der Destruktor wird nie aufgerufen und man hat nen fetten Memory Leak.
class CMyObject { double * m_pArray; std::fstream m_ofs; //... }; CMyObject::CMyObject() { m_pArray = new double[100]; m_ofs.open("C:\test.txt"); if (!m_ofs) throw std::exception("(ofstream) error while opening file."); } CMyObject::~CMyObject() { if(m_pArray) { delete m_pArray; m_pArray = NULL; } m_ofs.close(); }
Sorry, aber das ist wirklich schlechter Code. Alleine
if(m_pArray) { delete m_pArray; m_pArray = NULL; } m_ofs.close();
zeigt, dass du etliche Konzepte von C++ nicht verstanden hast. In deinem Destruktor ist jede einzelne Anweisung fragwürdig. Auch die Ungarische Notation ist ein Hinweis auf veraltete Stilrichtlinien.
- Zeiger müssen vor
delete
nicht auf Null geprüft werden. - Dynamische Arrays gibt man mit
delete[]
und nichtdelete
frei. - Das Nullsetzen nach dem
delete
ist absolut sinnlos hier. m_ofs.close()
ist auch unnötig, weilstd::fstream::~fstream()
die Datei automatisch schliesst.
Ich möchte dir jetzt nicht das ganze RAII-Konzept erklären. Aber grundsätzlich musst du deinen Konstruktor eben so konzipieren, dass er bei Fehlschlag aufräumt. Das kannst du manuell machen, aber wenn du moderne Dinge wie STL-Container einsetzt, ist das alles kein Problem. Ein
new double[100]
öffnet sowieso nur eine Wand von Problemen ohne wirkliche Vorteile. Du bist auf 100 Elemente beschränkt, verschwendest bei nur 2 Elementen Speicher, ausserdem hast du ohne Kopierkonstruktor und Zuweisungsoperator wieder Probleme (hier hast du Glück, dass die unkopierbaren Streams Kopiersemantik unterbinden).Wenn du das sauber implementierst, brauchst du nicht einmal einen Destruktor. Siehe Ryuzaki. Ich rate dir, die von Dravere geposteten Links zum Exception-Handling durchzulesen.
- Zeiger müssen vor
-
@Hans_Guck_In_Die_Luft
Das was du da machst ist mit RAII absolut kein Problem.class CMyObject { boost::scoped_array<double> m_pArray; std::fstream m_ofs; //... }; CMyObject::CMyObject() : m_pArray ( new double[100] ), m_ofs ( "C:\test.txt" ) { if (!m_ofs) throw std::exception("(ofstream) error while opening file."); // m_pArray } CMyObject::~CMyObject() { /* das braucht es alles gar nicht. Im übrigen: if(m_pArray) // unsinn { delete m_pArray; // falsch m_pArray = NULL; // für was? willst du m_pArray mach dem dtor noch benutzen? } */ // m_ofs.close(); // auch das ist überflüssig, da wenn m_ofs zerstört wird automatisch das objekt korrekt geschlossen wird }
Ich habe immmer mehr das Gefühl, dass du einfach nur C in C++ machen willst und dazu ein wenig eine schönere Syntax haben willst, um Objektorientiert zu arbeiten.
Das ist definitiv der falsche Weg und wie du siehst ist der Code leichter verständlich, vermeidet es unnötige Fehlerbehandlung vorzeitig zu machen und ist auch noch einfacher zu verwenden.
Du scheinst die einige Sachen in C++ nicht zu kennen, die das arbeiten sehr viel leichter machen und man sich eben nicht um die Dinge kümmern muss, die du ständig nennst. (Initialisierungsliste, RAII usw.).Wie du siehst gibt es für das Beispiel noch andere Möglichkeiten. Ich wollte nur mal zeigen, wie du das alleine durch RAII (und die Trennung der Aufgaben der Klassen) schon sehr viel besser machen kannst.
-
Das ist Pseudocode und soll andeuten, dass man Probleme hat, wenn eine Klasse Memory auf dem Heap anfordert und eine Exception im C'tor geflogen kommt.
Ich hab den Code nicht kompiliert. Es geht nur um die Idee dahinter. Flüchtigkeitsfehler bitte ich nachzusehen.Sorry, aber das ist wirklich schlechter Code. Alleine
C/C++ Code:if(m_pArray) { delete m_pArray; m_pArray = NULL; } m_ofs.close();
zeigt, dass du etliche Konzepte von C++ nicht verstanden hast. In deinem Destruktor ist jede einzelne Anweisung fragwürdig. Auch die Ungarische Notation ist ein Hinweis auf veraltete Stilrichtlinien.
@Nexus
Zeig mir mal bitte Projekt-Code von Dir. Haste was anzubieten? Bei Sourceforge oder so? Würde mal gerne modernen C++ Profi-Code von nem ausgewiesen Experten studieren.
-
Hans_Guck_In_Die_Luft schrieb:
Das ist Pseudocode und soll andeuten, dass man Probleme hat, wenn eine Klasse Memory auf dem Heap anfordert und eine Exception im C'tor geflogen kommt.
Ja, wenn man nicht mit modernem C++ entwickelt. Wenn man allerdings RAII einsetzt, ist es absolut NULL Problem. Ich empfehle dir nochmals die Artikel, welche ich erwähnt habe. Dort wird das Zusammenspiel von Exceptions und RAII wunderbar erläutert.
Wenn du C++ Code von ausgewiesenen Experten studieren möchtest, dann kauf dir entsprechende Literatur, das nützt am meisten. Zum Beispiel die ganze C++ In-Depth Series könnte man da empfehlen. Oder Effective C++ und More Effective C++ von Meyers.
Grüssli
-
Hans_Guck_In_Die_Luft schrieb:
Das ist Pseudocode und soll andeuten, dass man Probleme hat, wenn eine Klasse Memory auf dem Heap anfordert und eine Exception im C'tor geflogen kommt.
Les dir bitte mal etwas zum Thema RAII durch (ggf. auch mit Smartpointern). Und nein, es gibt keine Probleme mit Speicher auf den Heap, Konstruktoren in Zusammenhang mit Exceptions, wenn man sich mit C++ wirklich beschäftigt - da du scheinbar Posts nicht wirklich liest schenke ich mir aber Beispiele etc.
Ich empfehle auch die Bücher von Herb Sutter und Scott Meyers (Vor allem die Bücherreihen: Exceptional C++/Effectiv C++).
-
Hans_Guck_In_Die_Luft schrieb:
@Nexus
Zeig mir mal bitte Projekt-Code von Dir. Haste was anzubieten? Bei Sourceforge oder so? Würde mal gerne modernen C++ Profi-Code von nem ausgewiesen Experten studieren.Nein, ich habe bisher noch kein OpenSource-Projekt entwickelt. Vielleicht werde ich das mal machen, allerdings dürfte das einige Zeit dauern.
Ich bin aber bei Weitem kein C++-Profi oder -Experte. Ich kann zwar schon behaupten, dass ich mich etwas auskenne. Aber vieles habe ich durch dieses Forum und die wirklichen Experten (z.B. Herb Sutter) gelernt. Ich kann mich Dravere und asc nur anschliessen, die erwähnten Bücher lohnen sich allemal und zeigen dir genau solche Dinge auf (z.B. ist ein Grossteil von Exceptional C++ der Thematik gewidmet, exceptionsicheren Code zu schreiben).
Ansonsten solltest du dir die hier verlinkten Artikel anschauen, dort wird auch einiges erwähnt. Oder wenn du dich nach Smart-Pointers erkundigst, stösst du sicher ebenfalls auf RAII-Szenarien. Oder einfach mal auf Wikipedia suchen. Quellen gibt es mehr als genug; nicht zuletzt findest du bereits einiges heraus, wenn du die in diesem Thread geposteten Antworten und Codes genau studierst.