[Projekt] C++ Handler Library
Verfasst: 20.10.2016, 14:38
Abstract
Diese kleine Bibliothek erlaubt es C Objekte sicher in C++ Wrapper Klassen (gemäß RAII) zu kapseln.
Das Problem
In modernem C++ Code kommt man in der Regel oft mit C Interfaces in Kontakt. Diese beinhalten meist die Erstellung und Zertörung von irgendwelchen Objekten, welche in C++ ein Problem für die Exceptionsicherheit darstellen. Außerdem ist man es in C++ gewohnt, dass automatisch aufgeräumt wird, sobald ein Stackobjekt den Scope verlässt.
Wenn die Objekte im C Code durch Zeiger dargestellt werden, kann man diese mittels std::unique_ptr gut in C++ Code unterbringen:
Diese Vorgehensweise hat einen Nachteil: sie funktioniert nur, wenn die C API ihre Objekte mittels Zeigern darstellt. Desweiteren wird davon ausgegangen, dass die C API nullptr als ungültigen Zustand für ihre Zeiger verwendet. Wird z.B. der Wert INVALID_FILE_HANDLE im Fehlerfall zurückgegeben und dieser unterscheidet sich von nullptr, geht std::unique_ptr davon aus, dass dieser Zeiger gültig ist!
Features
Wie man anhand des Beispiels sieht, können auf Objekten des Typs ResourcePtr sowohl mit dem . als auch mit dem -> Operator Operationen ausgeführt werden. Hierbei ähneln die Methoden der ResourcePtr Klasse (welche durch den . Operator aufgerufen werden) stark den Methoden von std::unique_ptr, d.h. sie haben keinen Zusammenhang zu der zugrunde liegenden C API. Der Dereferenzierungsoperator -> hingegen arbeitet nur mit der C API! Diese Unterscheidung ermöglicht die saubere Trennung zwischen Arbeiten mit der API und hantieren mit Besitzverhältnissen.
Definitionen
RawHandle
Das RawHandle ist das eigentliche Objekt, das in der C API verwendet wird. Kann ein beliebiger POD-Typ sein (streng genommen muss er nicht mal POD sein, muss aber in einigen Operationen noexcept sein). Beispiele:
Eine beliebige Struktur, die folgendes Interface erfüllt:
Beispiel:
Handle<typename HandleTraits>
Eine abstrakte Klasse, die sich um die eigentlichen RAII Aufgaben kümmert. Wird nur als Basisklasse für den C Wrapper verwendet und hat damit für den Benutzer der Library erstmal keine Bedeutung. Beispiel:
HandlePtr<typename Handle>
Die Klasse, welche im User Code überall verwendet wird. Das Interface ist sehr stark an das von std::unique_ptr angelehnt. Beispiel:
Kommentar
Ich verwende diese Klassen in meinem eigenen Code bisher als Wrapper für POSIX file descriptoren und Speicherbereiche, die duch mmap erstellt werden. Ein Implementierung für Windows Fenster handles, für libwayland-client und Vulkan ist ebenfalls in Arbeit (funktionieren tun sie schon, fehlt nur noch der letzte Schliff). Also Praxistauglich ist mein Code schon :)
Alle Klassen befinden sich im Namespace ext. Für eine genauere Dokumentation bin ich momentan zu faul ;) Unit test sind jetzt auch irgendwan dran, da das Interface nun stabil ist.
Lizenz
Apache License, Version 2.0
Download
https://raw.githubusercontent.com/Biolu ... handle.hpp
Diese kleine Bibliothek erlaubt es C Objekte sicher in C++ Wrapper Klassen (gemäß RAII) zu kapseln.
Das Problem
In modernem C++ Code kommt man in der Regel oft mit C Interfaces in Kontakt. Diese beinhalten meist die Erstellung und Zertörung von irgendwelchen Objekten, welche in C++ ein Problem für die Exceptionsicherheit darstellen. Außerdem ist man es in C++ gewohnt, dass automatisch aufgeräumt wird, sobald ein Stackobjekt den Scope verlässt.
Wenn die Objekte im C Code durch Zeiger dargestellt werden, kann man diese mittels std::unique_ptr gut in C++ Code unterbringen:
Code: Alles auswählen
std::unique_ptr<FILE, decltype(&::fclose)> file{::fopen("datei", "r"), &::fclose};
Features
- Keine Abhängigkeiten
- Standard C++11
- Kleiner stack footprint (nicht mehr, als die C API selbst braucht!)
- Keine Heap Allokationen
- Zero cost abstraction (coole Buzz-Wörter!)
- Exception sicher
- Getrenntes Interface zwischen dem Wrapper und der eigentichen API, die gekapselt wird
- Late initialization (viele andere C Wrapper setzen bei der Initialisierung nur auf den Konstruktor, was oft zu Problemen führt)
- Kann in Kombination mit anderen C APIs verwendet werden
Code: Alles auswählen
// Eine Beispielhafte C API
using Resource = …;
Resource create_resource();
void destroy_resource(Resource r);
int use_resource(Resource r);
int consume_resource(Resource r); // Diese Funktion ergreift von der Resource Besitz, d.h. man darf sie nach diesem Aufruf nicht mehr mit destroy_resource() zerstören
// Hier überspringe ich vorerst die Details, wie ResourcePtr aufgebaut ist
int main()
{
// Erstelle die Resource
ResourcePtr resource;
resource->create();
// Verwende die Resource
resource->use();
// oder im C Stil:
use_resource(resource.get());
if (something)
throw std::runtime_error{u8"Irgendeine Exception"}; // Es ist sicher überall Exceptions zu werfen!
// Nun möchte eine C API wieder Besitz von der Resource ergreifen
consume_resource(resource.release());
resource->destroy(); // redundant; tut in diesem Fall hier nichts, weil die Resource mit .release() einen neuen Besitzer hat
}
Definitionen
RawHandle
Das RawHandle ist das eigentliche Objekt, das in der C API verwendet wird. Kann ein beliebiger POD-Typ sein (streng genommen muss er nicht mal POD sein, muss aber in einigen Operationen noexcept sein). Beispiele:
- int, als POSIX file descriptor
- HWND, als Windows Fenster handle
- std::pair<void*, size_t>, wobei der void* als Adresse und der size_t als Länge eines Speicherbereichs dient
Eine beliebige Struktur, die folgendes Interface erfüllt:
Code: Alles auswählen
struct HandleTraits
{
using RawHandle = …; // der C API handle Typ
static RawHandle const& invalid() noexcept; // liefert den Wert, der von der C API als ungültig angesehen wird
static void destroy(RawHandle const& handle) noexcept; // ist für das Aufräumen zuständig
};
Code: Alles auswählen
struct FileTraits
{
using RawHandle = FILE*;
// verzichte auf Referenzen, da ein Zeiger einfach kopiert werden kann
static constexpr RawHandle invalid() noexcept { return nullptr; } // constexpr ist okay
static void destroy(RawHandle h) noexcept { ::fclose(h); }
};
Eine abstrakte Klasse, die sich um die eigentlichen RAII Aufgaben kümmert. Wird nur als Basisklasse für den C Wrapper verwendet und hat damit für den Benutzer der Library erstmal keine Bedeutung. Beispiel:
Code: Alles auswählen
class File :
public Handle<FileTraits>
{
public:
using Handle::Handle; // importiere Konstruktoren
void open(char const* name, char const* mode)
{
close();
assert(raw_handle() == Traits::invalid());
m_raw_handle = ::fopen(name, mode);
if (raw_handle() == Traits::invalid())
throw std::runtime_error{u8"fopen"};
}
void close()
{
if (raw_handle() != Traits::invalid())
{
::fclose(raw_handle());
m_raw_handle = Traits::invalid();
}
}
};
Die Klasse, welche im User Code überall verwendet wird. Das Interface ist sehr stark an das von std::unique_ptr angelehnt. Beispiel:
Code: Alles auswählen
using FilePtr = HandlePtr<File>;
FilePtr file = nullptr;
file->open("datei", "r");
FILE* raw = file.get();
Ich verwende diese Klassen in meinem eigenen Code bisher als Wrapper für POSIX file descriptoren und Speicherbereiche, die duch mmap erstellt werden. Ein Implementierung für Windows Fenster handles, für libwayland-client und Vulkan ist ebenfalls in Arbeit (funktionieren tun sie schon, fehlt nur noch der letzte Schliff). Also Praxistauglich ist mein Code schon :)
Alle Klassen befinden sich im Namespace ext. Für eine genauere Dokumentation bin ich momentan zu faul ;) Unit test sind jetzt auch irgendwan dran, da das Interface nun stabil ist.
Lizenz
Apache License, Version 2.0
Download
https://raw.githubusercontent.com/Biolu ... handle.hpp