Copykonstruktor für binäre Bäume
-
volkard schrieb:
Warum auto_ptr?
Weil ein Zeiger zurückgegeben wird, der das allokierte Objekt besitzt, somit kommt ein nackter Zeiger schon aus Gründen der Exceptionsicherheit nicht in Frage. auto_ptr ist hier die beste Wahl
Warum nicht clone virtuell?
Weil dann keine Kovarianz möglich ist.
Und schließlich: Warum clone_impl private:
Weil virtuell ein Implementationsdetail ist.
-
camper schrieb:
volkard schrieb:
Warum auto_ptr?
Weil ein Zeiger zurückgegeben wird, der das allokierte Objekt besitzt, somit kommt ein nackter Zeiger schon aus Gründen der Exceptionsicherheit nicht in Frage. auto_ptr ist hier die beste Wahl
Dann mußt Du aus selbigem Grunde auch Wrapper um den new-Operator schreiben, damit er keine Zeiger mehr rausgibt, sondern smart pointers. Dann mußt Du praktisch alle rohen Zeiger wegmachen, damit machst Du C++ aber kaputt. Nimm besser Java.
camper schrieb:
Weil virtuell ein Implementationsdetail ist.
Und? Wozu das verstecken?
-
camper schrieb:
Warum nicht clone virtuell?
Weil dann keine Kovarianz möglich ist.
Das virtuelle clone() ist doch total voll mit Kovarianz.
Warum machst Du die clone() nicht einfach virtual, sondern gehst so einen Umweg? Ah, nur um den smart pointer durchzuboxen.
-
volkard schrieb:
Dann mußt Du aus selbigem Grunde auch Wrapper um den new-Operator schreiben, damit er keine Zeiger mehr rausgibt, sondern smart pointers. Dann mußt Du praktisch alle rohen Zeiger wegmachen, damit machst Du C++ aber kaputt. Nimm besser Java.
Wieso das? Es reicht doch, wenn man erst an der Schnittstelle nach aussen beginnt, den Zeiger zu kapseln. Intern kann ja nichts passieren,
operator newgibt den Speicher bei Fehlschlag automatisch frei.Mit deiner Aussage könnte man gegen alle Smart-Pointers argumentieren.
-
Nexus schrieb:
volkard schrieb:
Dann mußt Du aus selbigem Grunde auch Wrapper um den new-Operator schreiben, damit er keine Zeiger mehr rausgibt, sondern smart pointers. Dann mußt Du praktisch alle rohen Zeiger wegmachen, damit machst Du C++ aber kaputt. Nimm besser Java.
Wieso das? Es reicht doch, wenn man erst an der Schnittstelle nach aussen beginnt, den Zeiger zu kapseln. Intern kann ja nichts passieren,
operator newgibt den Speicher bei Fehlschlag automatisch frei.clone ist ganz genau wie new. Intern kann ja nichts passieren.
clonegibt den Speicher bei Fehlschlag automatisch frei.
-
Ja,
clone()muss bei Fehlschlag aufräumen, damit die Exceptionsicherheit eingehalten wird.Die Frage ist eher, wer für den Zeiger zuständig ist, wenn die Allokation erfolgreich verlaufen ist. Aber ich glaube, ich verstehe, was du meinst; da der zurückgegebene Zeiger danach höchstwahrscheinlich direkt in einem Container gespeichert wird, führt
std::auto_ptrnur dazu, ständigrelease()aufzurufen.
-
volkard schrieb:
camper schrieb:
volkard schrieb:
Warum auto_ptr?
Weil ein Zeiger zurückgegeben wird, der das allokierte Objekt besitzt, somit kommt ein nackter Zeiger schon aus Gründen der Exceptionsicherheit nicht in Frage. auto_ptr ist hier die beste Wahl
Dann mußt Du aus selbigem Grunde auch Wrapper um den new-Operator schreiben, damit er keine Zeiger mehr rausgibt, sondern smart pointers.
Dem kann ich nicht folgen.
volkard schrieb:
Dann mußt Du praktisch alle rohen Zeiger wegmachen, damit machst Du C++ aber kaputt.
Ich muss annehmen, das wir verschiedene Dinge an C++ wertschätzen - rohe Zeiger gehören bei mir eher nicht dazu. Und ich bin durchaus davon überzeugt, dass besitzende rohe Zeiger im öffentlichen Interface nichts zu suchen haben.
volkard schrieb:
camper schrieb:
Weil virtuell ein Implementationsdetail ist.
Und? Wozu das verstecken?
Um sich Flexibilität zu erhalten.
Und in diesem speziellen Fall: bei zwei Funktionen, die das gleiche tun, bei der die Benutzung einen sicherer ist als die andere, ohne dass diese Sicherheit durch Overhead beim Programmlauf oder bei der Formulierung des aufrufenden Codes erkauft wird, ist die weniger sichere Funktion als Teil des Interfaces überflüssig.volkard schrieb:
camper schrieb:
Warum nicht clone virtuell?
Weil dann keine Kovarianz möglich ist.
Das virtuelle clone() ist doch total voll mit Kovarianz.
Warum machst Du die clone() nicht einfach virtual, sondern gehst so einen Umweg? Ah, nur um den smart pointer durchzuboxen.Man kann es auch anders herum sehen: ist clone virtuell, besteht von vornherein nicht die Möglichkeit, sich für oder gegen Smartpointer in Verbindung mit Kovarianz zu entscheiden. virtuell ist eine Einschränkung.
Im Übrigen geht es hier bei der Exceptionsicherheit nicht um den Aufruf selber (da gibt es logischerweise keinen Unterschied, schließlich ruft die Smartpointerüberladung die andere auf), sondern um das, was nach der Rückkehr aus der Funktion passiert. In jedem Fall sind das nur die üblichen Argumente, die für bzw. gegen Smartpointer vorzubringen sind; wenn die dich im Allgemeinen nicht überzeugen, wird das hier in diesem speziellen Fall nicht anders sein - und umgekehrt.
Nicht zu unterschätzen ist zudem der Dokumentationseffekt: ein auto_ptr besitzt das Objekt, auf das er zeigt; ob das bei einem rohen Zeiger der Fall ist, erschließt sich hingegen nur aus dem Kontext.
-
Schauen wir doch einfach mal, was die smartpointers im clone da bringen, indem wir von rohen Zeigern ausgehen und einen Fehler basteln.
Gefahr lauer beiverbrauche(a.clone(),b.clone());wenn in das zeitlich später aufgerufene clone() nicht klappt.
Schauen wir jetzt mal
verbrauche(new Node1(*a),new Node2(*b));und stellen fest, daß da kein Unterschied ist.
Willst Du jetzt clone einwickeln, wäre es auch logisch, daß Du new einwickelt.
template<typename T> auto_ptr<T> create(T const& t){ return auto_ptr<T>(new T(t)); }und schon ist auch bei
verbrauche(create(*a),create(*b));das Problem weg.
Und Du solltest den Weg ganz beschreiten, wenn Du meinst, das sei ok.
Ich meine, daß das da total übertrieben ist. sowohl bei new als auch bei clone. Es gibt andere sonnvolle Verwendungen für smart pointers, aber hier nicht. Ich wertschätze nicht die rohen Zeiger als solche, aber die Einfachheit schätze ich.
Mir scheint, deine smart pointers lösen nur das gar nicht real existierende Problem mit
verbrauche(a.clone(),b.clone());und sind deswegen recht überflüssig.
-
volkard schrieb:
Schauen wir doch einfach mal, was die smartpointers im clone da bringen, indem wir von rohen Zeigern ausgehen und einen Fehler basteln.
Gefahr lauer beiverbrauche(a.clone(),b.clone());wenn in das zeitlich später aufgerufene clone() nicht klappt.
Schauen wir jetzt mal
verbrauche(new Node1(*a),new Node2(*b));und stellen fest, daß da kein Unterschied ist.
Willst Du jetzt clone einwickeln, wäre es auch logisch, daß Du new einwickelt.
template<typename T> auto_ptr<T> create(T const& t){ return auto_ptr<T>(new T(t)); }und schon ist auch bei
verbrauche(create(*a),create(*b));das Problem weg.
Wenn wir new zulassen wollten, bräuchten wir kein clone - folglich wäre new nicht in ein create einzupacken, sondern ganz zu verhindern (per privater new Überladung oder indem wir die Konstruktoren private machen und eine Factory benutzen - das ist bei solchen Problemen ohnehin bereits häufig aus anderen Gründen der Fall; das eine andere Baustelle).
volkard schrieb:
Und Du solltest den Weg ganz beschreiten, wenn Du meinst, das sei ok.
Richtig, halbe Sachen - insbesondere wenn die Wahl im Einzelfall willkürlich erscheinen muss - sind keine gute Idee.
volkard schrieb:
Mir scheint, deine smart pointers lösen nur das gar nicht real existierende Problem mit
verbrauche(a.clone(),b.clone());und sind deswegen recht überflüssig.
Ob dieses spezielle Problem existiert oder nicht, spielt nur bedingt eine Rolle. Wenn ich
sink(a.clone(),do_something_that_might_throw());betrachte, bin ich jedenfalls absolut nicht sicher, dass das nie auftritt. Und seltsam erscheint es mir auch nicht. Zudem ist vermutlich nicht in jedem Falle auf Anhieb zu erkennen, ob ein Stück Code diese Form hat oder nicht. An dieser Stelle defensiv zu programmieren ist nur mit geringen Kosten verbunden. Und mit Blick auf die Zukunft (move-Konstruktion+perfektes Forwarding) erwarte ich eher, dass Funktionsaufrufe vermehrt so direkt miteinander verknüpft werden.
Allein die Tatsache, dass du meinst, das Problem wäre nur auf zwei clone-Aufrufe gleichzeitig beschränkt, obwohl das nicht der Fall ist, demonstriert hinreichend, dass Vorsicht an dieser Stelle kaum verkehrt sein kann.
-
camper schrieb:
Wenn wir new zulassen wollten, bräuchten wir kein clone - folglich wäre new nicht in ein create einzupacken, sondern ganz zu verhindern
Dann denk dir new für eine andere Klasse oder new für einen anderen Konstruktor. Ich meinte JEDES new.
camper schrieb:
Wenn ich
sink(a.clone(),do_something_that_might_throw());betrachte, bin ich jedenfalls absolut nicht sicher, dass das nie auftritt.
Also gleiches Problem mit
sink(new int(5),do_something_that_might_throw());. Wie löst man das möglichst gut, ohne beim Ersten Parameter auf Wertübergabe zu wechseln?
-
volkard schrieb:
sink(new int(5),do_something_that_might_throw());. Wie löst man das möglichst gut, ohne beim Ersten Parameter auf Wertübergabe zu wechseln?
gar nicht. Es sieht auch gefährlich aus. new erzeugt rohe Zeiger, also ist hier automatisch mehr Aufmerksamkeit gefordert, so wie man genauer hinschaut bei delete,allen Arten von Casts etc.
sink(a.clone(),do_something_that_might_throw());dagegen sieht harmlos genug aus - Grund genug, dafür zu sorgen, dass es auch tatsächlich harmlos ist.