Hilfestellung für exception-Sicherheit und RAII
-
Mach doch ein Typedef.
-
Ich persönlich bevorzuge die Variante mit der eigenen kapselnden Klasse.
Du erhöhst damit ein wenig die Abstraktion und machst dem durch den Code sozusagen eher klar, was deine Entität macht als wenn du sie nur in einen unique_ptr einkapselst. Und du erreichst etwas Flexibilität für später, falls/wenn du mal deine Interna austauschst, sprich OpenSSL::X509 durch ClosedSSL::KeyStuff ersetzen willst.
-
@Techel: beim Typedef weiß ich gar nicht so recht was ich da machen könnte. Kannst du ein kurzes Beispiel geben, wie so etwas aussehen könnte?
@Skym0sh0: Wäre es sinnvoll ein Klasse zu erstellen, die gleich mehrere Datentypen verwaltet? Was mich ein wenig von der Klasse abschreckt, ist meine aktuelle Vorstellung davon, für jeden Datentyp eine Klasse zu erstellen. Sollte ich später mal Änderungen machen (z.B. erweitern), dann müsste ich jede Klasse anpassen.
Es gibt ja dieses 'typeid(meineVariable).name()', das den Typ einer Variable identifiziert. Eventuell könnte man irgendwie den gelieferten Pointer (über den Konstruktor) in ein void* packen und den Datentyp als std::string speichern. Beim Löschen wird dann geschaut, welcher Datantyp es ist. ...oder das ist völliger Blödsinn. ...ist so eine Idee
-
Na zum Beispiel soetwas:
typedef unique_ptr<ASN1_OCTET_STRING, decltype(ASN1_STRING_delete)> X509Guard
Da hast du dann im Grunde das gleiche wie wenn du eine neue Klasse erstellst.
Deine letzte Idee ist tatsächlich Blödsinn, das fügt nur unnötigen Overhead hinzu
-
ach mensch natürlich
was würde ich nur ohne euch machen. Wenn ihr meine Nachbarn wärt, dann würde ich euch zum Grillen und eine Flasche Bier einladen
-
Ich verwende bei sowas z.T. auch einfach
shared_ptr
.
Der Vorteil beishared_ptr
ist dass der Typ des Deleters sich nicht im Smart-Pointer-Typ niederschlägt.
Damit kann man dann schön Factory-Funktionen machen.
Die Factory-Funktionen kann man dann verwenden ohne sich darum kümmern zu müssen wie das Objekt jetzt wirklich gelöscht wird.
Ist vielleicht nicht die "schönste" Lösung (weil man shared-Ownership auch dort ermöglicht wo man es gar nicht braucht, und natürlich den shared_ptr Overhead dadruch auch unnötig mitschleppt). Aber für viele Anwendungen vollkommen ausreichend.Ansonsten, wenn dein Deleter Default-Constructible ist, dann kannst du natürlich auch einen
unique_ptr
typedef verwenden. Dann musst du auch nichts unnötig wiederholen. Durch den typedef vermeidest du den Deleter-Typ überall zu wiederholen und dadurch dass er Default-Constructible ist vermeidest du die Konstruktor-Parameter für den Deleter überall zu wiederholen.
-
Ja stimmt
Somit hätte ich wohl folgende Varianten:
static auto X509_deleter = [](X509* t) {X509_free(t);}; typedef std::unique_ptr<X509, decltype(X509_deleter)> X509_Guard; // unique_ptr ohne typedef std::unique_ptr<X509, decltype(X509_deleter)> myVar1(nullptr, X509_deleter); // unique_ptr mit typedef X509_Guard myVar2(nullptr, X509_deleter); // shared_ptr std::shared_ptr<X509> myVar3(nullptr, X509_free);
Noch mal eine Frage am Rande. Kann ich sowas problemlos machen?:
std::shared_ptr<X509> meineFunktion() { std::unique_ptr<X509, decltype(X509_deleter)> myVar1(X509_new(), X509_deleter); return myVar1.release(); } std::shared_ptr<X509> meineFunktion() { std::shared_ptr<X509> myVar1(X509_new(), X509_free); // Variante 1 return myVar1.release(); // Variante 2 return myVar1; }
... für den Fall, dass ich einen Pointer teilen will
(habe es gerade noch nicht getestet)
-
SBond schrieb:
Noch mal eine Frage am Rande. Kann ich sowas problemlos machen?:
std::shared_ptr<X509> meineFunktion() { std::unique_ptr<X509, decltype(X509_deleter)> myVar1(X509_new(), X509_deleter); return myVar1.release(); }
Nein.
Problem #1 Der Deleter wird nicht automatisch mit übernommen. Du musst ihn beim Erzeugen desshared_ptr
nochmal angeben.
Problem #2 Die Konstruktoren vonshared_ptr
die einenT*
nehmen sindexplicit
.So sollte es gehen
std::shared_ptr<X509> meineFunktion() { std::unique_ptr<X509, decltype(X509_deleter)> myVar1(X509_new(), X509_deleter); return std::shared_ptr<X509>(myVar1.release(), myVar1.get_deleter()); // Oder auch einfach return move(myVar1); }
SBond schrieb:
std::shared_ptr<X509> meineFunktion() { std::shared_ptr<X509> myVar1(X509_new(), X509_free); // Variante 1 return myVar1.release(); // Variante 2 return myVar1; }
Variante 1: Nein.
shared_ptr
hat keine Release Funktion. Darf auch keine haben.Variante 2: Ja, das geht.
-
Ich möchte aber nochmal auf die Sache mit default konstruierbaren Deletern hinweisen:
struct X509_deleter { void operator()(X509* p) { if (p) X509_free(p); } }; typedef std::unique_ptr<X509, X509_deleter> X509_Guard; void Foo() { X509_Guard g(X509_new()); // ... }
Ist mMn. viel angenehmer zu verwenden.
Ich mag auf jeden Fall keinen Code wo ich jedes mal wo ich ein Objekt erstelle noch unnötigerweise mit angeben muss wie es gelöscht werden soll.
-
Meiner Meinung sollte die Klasse X509 heissen, das X509_new() gehört in den Konstruktor und die Klasse sollte alle fachlichen Methoden haben um mit dem Objekt zu arbeiten.
-
SBond schrieb:
Ja stimmt
Noch mal eine Frage am Rande. Kann ich sowas problemlos machen?:
std::shared_ptr<X509> meineFunktion() { std::unique_ptr<X509, decltype(X509_deleter)> myVar1(X509_new(), X509_deleter); return myVar1.release(); } std::shared_ptr<X509> meineFunktion() { std::shared_ptr<X509> myVar1(X509_new(), X509_free); // Variante 1 return myVar1.release(); // Variante 2 return myVar1; }
Ja das war wiklich Blödsinn von mir. Aber der Compiler klopft einen da ja auf die Finger :). Tatsächlich ist der move()-Befehl hier die richtige Wahl. Hatte vor einiger Zeit etwas darüber gelesen, aber da ich noch keine Anwendung fand, geriet es wieder in Vergessenheit. Auch nochmal vielen Dank für den Tipp mit der ()-Überladung. Das ist eine echt gute Ergänzung
nochmals vielen Dank für die tolle Unterstützung von euch
Damit lässt sich der Quellcode viel besser exception-sicherer gestalten, als meine ersten Versuche mit try/catch in jeder Funktion. *freu*