RAI vs std::unique_ptr
-
@Steffo sagte in RAI vs std::unique_ptr:
Ich weiß nicht, ob wir vom selben Reden. Im obigen Beispiel hätte ich bla_instance als Roh-Pointer nehmen können und hätte mit RAI alles abgedeckt. Ein std::unique_ptr wäre vollkommen unnötig gewesen. Wozu nehmen hier manche std::unique_ptr?
Wegen dem Single Responsibility Principle -- eine Klasse sollte nur eine Aufgabe haben. Deine Klasse hat zusätzlich zu ihrer eigentlichen Aufgabe noch die Aufgabe, eine Resource zu verwalten. Mit unique_ptr übernimmt das eine andere Klasse.
Es heißt übrigens RAII.
-
Hier mal ein vollständiges Beispiel, wie man das mit RAI macht:
class foo { public: foo() { bla = new bla(); } ~foo() { delete bla; } private: bla* bla_instance; }
Zurück zu meiner Ausgangsfrage
Wozu std::unique_ptr?
-
foo f1; foo f2; f1 = f2; // Bumm
-
Das ist aber kein RAII - weil du deinen Speicher "von Hand" aufräumst
Online-Testen: cpp.sh/4w6ds
#include <iostream> #include <memory> struct test { test(){ std::cout << "test.ctor" << std::endl; } ~test(){ std::cout << "test.dtor" << std::endl; } }; struct test2 { test2():t(new test()){} std::unique_ptr<test> t; }; struct test3 { test3():t(new test()){} test* t; }; int main() { test2(); test3(); }
Ausgabe:
test.ctor
test.dtor
test.ctor
-
Naja, er macht schon sowas wie RAII. Nur halt manuell und unvollständig. Wenn er das fehlerfrei und vollständig komplettiert hat er aber genau das, was
std::unique_ptr
macht. Und dann kann man sich natürlich fragen, warum man den nicht sofort benutzt hat.
-
@llm sagte in RAI vs std::unique_ptr:
Das ist aber kein RAII - weil du deinen Speicher "von Hand" aufräumst
Online-Testen: cpp.sh/4w6ds
#include <iostream> #include <memory> struct test { test(){ std::cout << "test.ctor" << std::endl; } ~test(){ std::cout << "test.dtor" << std::endl; } }; struct test2 { test2():t(new test()){} std::unique_ptr<test> t; }; struct test3 { test3():t(new test()){} test* t; }; int main() { test2(); test3(); }
Ausgabe:
test.ctor
test.dtor
test.ctortest3 hat ja auch ein Leak. Bevor es std::unique_ptr gab, ist man doch genau so vorgegangen und nannte das RAII, oder nicht?
-
Klar - aber der unique_ptr forciert das RAII Konzept für Heap-Dinge
-
@manni66 sagte in RAI vs std::unique_ptr:
foo f1; foo f2; f1 = f2; // Bumm
Nicht wirklich: http://cpp.sh/8rzfo
-
@Steffo sagte in RAI vs std::unique_ptr:
Nicht wirklich:
Doch wirklich. Memory leak und double free.
-
@manni66 sagte in RAI vs std::unique_ptr:
@Steffo sagte in RAI vs std::unique_ptr:
Ich sehe da sogar Nachteile, da bei Vorwärtsdeklarationen auch noch eine Funktion für das Delete mitgegeben werden muss,
Den Satz verstehe ich nicht.
Das hier funktioniert nicht.
// Vorwärtsdeklaration class bla; class foo { public: foo(); // Instanziierung von Member-Variablen ~foo(); // Bei Raw-Pointern wird hier delete gemacht private: std::unique_ptr<bla> bla_instance; }
-
-
@manni66 sagte in RAI vs std::unique_ptr:
@Steffo
In die cppfoo::~foo() = default;
und schon geht's.
Ich kann dir wirklich nicht folgen. Es kompiliert und der Konstruktor wird zwei mal aufgerufen:
-
@Steffo sagte in RAI vs std::unique_ptr:
@manni66 sagte in RAI vs std::unique_ptr:
@Steffo
In die cppfoo::~foo() = default;
und schon geht's.
Ich kann dir wirklich nicht folgen. Es kompiliert und der Konstruktor wird zwei mal aufgerufen:
was hat das mit dem zitierten Code zu tun?
@manni66 sagte in RAI vs std::unique_ptr:
Doch wirklich. Memory leak und double free.
Wo kommt hier "Konstruktor wird nicht aufgerufen" vor?
Siehe auch http://cpp.sh/7hjid
-
@Steffo: Sieh dir mal die Ausgabe von
t
an: dein Code mit erweiterter AusgabeVllt. verstehst du jetzt, daß du so auch den Zuweisungsoperator
=
manuell erstellen mußt?
-
@Th69
Mit std::unique_ptr habe ich das Problem nicht?
Ich werde mir das mal nach der Arbeit genauer anschauen. Danke vorab erst mal.
-
@Steffo
Mitunique_ptr
hast du ein anderes Problem: Er ist nicht kopierbar, d.h. du musst die Kopierkonstuktoren und Zuweisungsoperatoren implementieren. Wobei.... die move-Semantik funktioniert dankunique_ptr
automatisch, die Kopiersemantik aber nicht.
-
@DocShoe sagte in RAI vs std::unique_ptr:
Er ist nicht kopierbar,
Was ja auch gewünscht ist. Will man kopieren können, muss man genau die Funnktionen schreiben, die man auch im Rawpointer-Fall benötigt hätte. Schreibt man die Funktionen nicht, haut einem der Compiler auf die Finger. Im Rawpointer-Fall hat man UB.
-
Ja, den Wunsch drückt man ausdrücklich durch die Verwendung von
std::unique_ptr
aus. Steffos Variante und diestd::unique_ptr
Variante verhalten sich aber unterschiedlich, und Steffo hat nicht gesagt, dass er Kopien verhindern will. Also gehe ich davon aus, dass Kopien möglich sein sollen und das geht mit´mstd::unique_ptr
halt so nicht.
-
Es gibt noch eine ganze weitere Dimension bei der Geschichte: Mehrere Ressourcen. Oder allgemein andere Klassenmember, die bei Initialisierung werfen können. Hat man nämlich einen rohen Zeiger und einer der anderen Member wirft bei Initialisierung etwas, dann hat man ein echt grässliches Problem: Ist dem Zeiger nun eine Ressource zugewiesen worden, oder nicht? Der Destruktor will einfach deleten, aber das geht schief, wenn die Ressource nie belegt wurde (Exceptions im Destruktor sind dein Tod!).
Jetzt gibt es zwar die berüchtigten function-level-try-blocks:
# Für die, die das nicht kennen (Braucht man auch nicht wissen!) MyClass() try: ptr1(Resource(), may_throw() {} catch (...) {delete ptr1; throw;}
Aber was machst du, wenn
Resource()
etwas wirft? Man brächte hier echt viel schrecklichen Code, um alles abzudecken. Und ich meine sogar, für zwei oder mehr Ressourcen geht das prinzipiell überhaupt gar nicht, alle Fälle abzudecken.Wenn man dedizidierte Handlerklassen benutzt, die jeweils nur genau eine Ressource verwalten (und daher intern dieses Problem nicht haben können), dann bekommt man die perfekte Lösung gratis geschenkt, denn der Destruktor ist dann der Standarddestruktor und der weiß ganz genau, welche Member schon initialisiert waren, als die Exception flog, und kann genau die richtigen wieder abräumen.Damit kann man dann auch viele verschiedene Ressourcen belegen, solange man dies indirekt über Handler tut.
(Und daher kennt auch niemand function-level-try-blocks, denn die braucht man nur, wenn der Code sowieso scheiße ist, aber wer schlechten Code schreibt, der kennt auch keine function-level-try-blocks)
-
Dieser Beitrag wurde gelöscht!