[Projekt] C++ Handler Library

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Antworten
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

[Projekt] C++ Handler Library

Beitrag von Biolunar »

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:

Code: Alles auswählen

std::unique_ptr<FILE, decltype(&::fclose)> file{::fopen("datei", "r"), &::fclose};
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
  1. Keine Abhängigkeiten
  2. Standard C++11
  3. Kleiner stack footprint (nicht mehr, als die C API selbst braucht!)
  4. Keine Heap Allokationen
  5. Zero cost abstraction (coole Buzz-Wörter!)
  6. Exception sicher
  7. Getrenntes Interface zwischen dem Wrapper und der eigentichen API, die gekapselt wird
  8. Late initialization (viele andere C Wrapper setzen bei der Initialisierung nur auf den Konstruktor, was oft zu Problemen führt)
  9. Kann in Kombination mit anderen C APIs verwendet werden
Beispiel

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
}
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:
  • 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
HandleTraits
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
};
Beispiel:

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); }
};
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:

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();
		}
	}
};
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:

Code: Alles auswählen

using FilePtr = HandlePtr<File>;
FilePtr file = nullptr;
file->open("datei", "r");
FILE* raw = file.get();
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
Benutzeravatar
Jonathan
Establishment
Beiträge: 2353
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [Projekt] C++ Handler Library

Beitrag von Jonathan »

Sieht nett aus :)

Ich habe derzeit keinen konkreten Bedarf, aber ich werde versuchen, es im Hinterkopf zu behalten und dann ggf. mal auszuprobieren.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Antworten