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