Hilfestellung für exception-Sicherheit und RAII
-
Ich denke die Wartbarkeit und der Lebenszyklus der Software spielen schon eine Rolle
Naja... wenn meine Software wirklich gut sein sollte, dann bräuchte ich noch 1-2 Optimierungsdurchläufe. Ich sehe es an den Bereichen, bei denen ich gerade mit C++ angefangen hatte. Zumindest habe ich in den letzten Monaten einige Fortschritte erziehlt.Nochmal kurz zu den Tipp mit der eigenen Klasse. Da ist natürlich was dran. Momentan sehe ich zwei Möglichkeiten meine bestehenen Funktionen zu verbessern:
1. mit unique_ptr/shared_ptr:
Header:
static auto X509_closer = [](X509* t) {X509_free(t);};
Cpp:
void meineFunktion () { unique_ptr<X509, decltype(X509_closer)> myNewCert(X509_new(), X509_closer); // ...irgendwelcher code ... }
2. mit eigener Klasse:
Header:
class X509Guard { public: X509Guard(X509 *x509) { m_x509 = x509; } ~X509Guard() { free(); } void free() { if (m_x509 != nullptr) { X509_free(m_x509); } } X509 *get() { return m_x509; } void reset(X509 *x509) { free(); m_x509 = x509; } X509 *release() { X509 *tmp = m_x509; m_x509 = nullptr; return tmp; } private: X509 *m_x509; };
Cpp:
void meineFunktion () { X509Guard myNewCert(X509_new()); // ...irgendwelcher code ... }
Könnt ihr eine Empfehlung geben, welche Variante die bessere ist? Die eigene Klasse hat zumindest den Vorteil, dass diese besser zu lesen ist. Allerdings ist funktionalität im Vergleich zu unique_ptr geringer bzw. ich müsste diese nachimplementieren. Außerdem müsste ich wohl für jeden Datentyp eine Klasse erstellen, da einige Typen verschiedene deleter haben (z.B. von der Anzahl der Parameter). ...ein Template würde wohl nicht so ohne weiteres gehen.
Oder kann man irgendwie beides Kombinieren? Also ein unique_ptr den ich auch genau so verwende, aber die Initialisierung wie bei meinem 'X509Guard' mache (d.h. die Angabe des deleters wird irgendwie ausgelagert)? ...möglicherwise ginge es über Makros. Aber selbst wenn, wäre es bestimmt keine gute Lösung.
In erster Linie würde ich zu Variante 1 tendieren, da diese etwas weniger Aufwand darstellt und weniger Code benötigt wird. Mein Problem sind Ausdrücke wie diese hier (Code-Auszug):
unique_ptr<ASN1_OCTET_STRING, decltype(ASN1_STRING_delete)> subKeyId(nullptr, ASN1_STRING_delete); subKeyId.reset(static_cast<ASN1_OCTET_STRING*>(X509_get_ext_d2i(x509 ,nid, nullptr, nullptr)));
Das ist schon in zwei Zeilen aufgebrochen und sieht dennoch unübersichtlich aus. So etwas ist in meinem Code nur 2-3 mal vorhanden, aber ich denke immer, das geht doch bestimmt übersichtlicher.
-
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*