Bibliotheksschnittstelle mit C++0x

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Unknown GER
Beiträge: 49
Registriert: 09.01.2003, 13:04

Bibliotheksschnittstelle mit C++0x

Beitrag von Unknown GER »

Ich bin kurz davor eine Bibliothek in C++ bzw. C++0x zu schreiben und stehe vor der Entscheidung, wie das Interface aussehen soll. Die Bibliothek soll sehr effizient sein und trotzdem ein sauberes, übersichtliches Interface bieten. Ich hab mir gestern ein paar Webcasts zu C++0x reingezogen, doch die Informationsflut ist erst einmal erschlagend. Daher würde ich gerne fragen, ob ihr schon Erfahrungen sammeln konntet.

Meine bisherigen Vorgehensweisen bei Bibliotheken waren folgende drei (stark konstruierte, künstliche Beispiele ohne Anspruch auf Vollständigkeit):

Direkt

Die Header-Dateien der implementierenden Klassen sind öffentlich zugänglich.

Code: Alles auswählen

class Window
{
public:
    void setSize(int width, int height);

private:
    int width;
    int height;
};
+ schnell
+ maximale Verwendung des Stacks, keine unnötige Verwendung des Heaps

- private Implementierungsdetails komplett sichtbar
- "Interface" geht schlichtweg unter

Pimpl

Implementierungsdetails sind in einer klasseninternen Struktur verborgen.

Code: Alles auswählen

class Window
{
public:
    void setSize(int width, int height);

private:
    struct Impl;
    Impl *impl; // od. auto_ptr
};

Code: Alles auswählen

struct Window::Impl
{
    int height;
    int width;
};

Window::Window()
    : impl(new Impl)
{
}

Window::~Window()
{
    delete impl;
}

void Window::setSize(int width, int height)
{
	impl->width = width;
	impl->height = height;
}
+ Öffentliches Interface ist weitaus sauberer
+ Keine Factories zur Erzeugung nötig
+ Aufrufer muss sich nicht um delete kümmern

- Ziemlich starker Overhead durch Heap und kein direkter Zugriff auf Member
- struct Impl und Impl *impl sind noch im öffentlichen Interface (noch verschmerzbar)

Interface

Der Klassiker mit abstrakten Klassen, die dem Interface-Konzept entsprechen.

Code: Alles auswählen

class IWindow
{
public:
    HWND getHandle() const = 0;
    virtual ~IWindow() {}
};

IWindow * createWindow(); // Factory-Funktion

Code: Alles auswählen

class Window : public IWindow
{
public:
    HWND getHandle() const;

private:
    HWND handle;
};

// .CPP
HWND Window::getHandle() const
{
    return handle;
}
Es folgt ein äußerst unbequemer Nachteil:

Code: Alles auswählen

class IApplicationWindow : public IWindow
{
public:
    // ...
    virtual ~IApplicationWindow() {}
};

IApplicationWindow * createApplicationWindow();

Code: Alles auswählen

class ApplicationWindow : public IApplicationWindow : public Window
{
public:
    HWND getHandle() const; // Macht keinen Spaß!
};

// .CPP
HWND ApplicationWindow::getHandle() const // Wirklich nicht! :-(
{
    return Window::getHandle();
}
+ Schnittstelle in Reinform und übersichtlich

- Factories
- Wieder spielt sich eigentlich alles im Heap ab
- Aufrufer muss sich um delete kümmern
- Sehr unschöner Programmier-Overhead wenn öffentliche Schnittstellen voneinander erben (siehe oben)

Resumée

Schnellste Lösung ist die direkte Methode, keine Frage. Es macht aber keinen Spaß eine solche Bibliothek zu verwenden.
Die beiden anderen Methoden werkeln auf dem im Vergleich zum Stack lahmen Heap rum.
Interfaces übetragen dem Aufrufer eine unnötig hohe Verantwortung (delete).

Vorteile durch C++0x
Die Factories könnten shared_ptrs zurückgeben. Kein delete vom Aufrufer mehr nötig. Factories an sich sind auch nicht unsauber. Wem das nicht gefällt, soll eine rein objektorientierte Sprache nutzen, der Mix aus prozeduraler und objektorientierter Programmierung ist meiner Meinung nach einer der größten Vorteile von C++ den man nutzen sollte! Es fördert sogar die Kapslung (siehe einschlägige Literatur von Sutter/Herb). Trotzdem bleibt das Problem der Schnittstellenvererbung. Das macht keinen Spaß zu programmieren durch die ganzen Weiterleitungen der eigentlichen Methodenaufrufe an die Basisklassen. Immerhin bekommt der Bibliotheksnutzer nichts davon mit.

Bei den Impls könnte/sollte man nun den unique_ptr verwenden, aber es ändert glaube ich nichts an dem Umweg für jeden Member.

Ich habe die ganze Zeit das Gefühl, etwas grundlegendes noch zu übersehen, dass das ganze vereinfachen würde. Vorteile mit der Move-Semantik bei den Factories gibt es ja nicht, weil ich durch die abstrakten Schnittstellen Zeiger zurück geben muss. Oder gibt es da eine clevere Alternative? Ich würde ungern so viel auf dem Heap arbeiten.

Habt ihr noch Ideen, Tipps & Tricks oder Anregungen?
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: Bibliotheksschnittstelle mit C++0x

Beitrag von Aramis »

Reine Interfaces arbeiten halt nicht so irre toll mit RAII zusammen. Wenn keine 'Notwendigkeit' fuer Interfaces besteht - d.h. die Typen nicht wirkich polymorph sein muessen sondern du nur die Member verstecken willst - wuerde ich sie auch nicht verwenden.
Sehr unschöner Programmier-Overhead wenn öffentliche Schnittstellen voneinander erben (siehe oben)
Ich denke dass Diamanten oder 'Halbdiamanten' in vielen Faellen Designfehler sind. Ich kann mich nicht dran erinnern, jemals in die Situation gekommen zu sein doppelte Basisklassenmember als Dummies neu definieren zu muessen um den Compiler ruhig zu stellen.

Du solltest in deinen Bibliotheksklassen, sofern sinnvoll, auch Move-Konstruktoren und Move-Assignment-Operatoren anbieten. Der Compiler generiert sie automatisch wenn er kann, sofern kein Default-Konstruktor definiert ist. Also lieber Konstruktoren ausschreiben oder ihn explizit mit der neuen 0x-Syntax davon abhalten.
Die Factories könnten shared_ptrs zurückgeben.
shared_ptr's als Rueckgabewert fuer eine Factory? Wieso, der User kann den zurueckgegebenen Pointer ja auch manuell an den shared_ptr/scoped_ptr/auto_ptr/EigenerSmartPointer-Konstruktor uebergeben. Wird direkt ein Smart-Pointer zurueckgegeben, so wird der User dazu gezwungen exakt diesen zu nutzen.
Unknown GER
Beiträge: 49
Registriert: 09.01.2003, 13:04

Re: Bibliotheksschnittstelle mit C++0x

Beitrag von Unknown GER »

Mit den shared_ptr's hast du natürlich recht. Ich bevorzuge es eigentlich auch, nicht unnötig restriktiv zu sein. Ich schreibe auch lieber "const char *" in Signaturen und wenn nötig, packe sie erst in der Methode in einen string (Hat das eigentlich irgendwelche Nachteile?).

Das Problem mit den Interfaces hatte ich bisher auch noch nicht, aber im aktuellen Projekt würde kein sinnvoller Weg drum rum führen, wenn Schnittstellen und Implementation strikt getrennt sind. Der Compiler verlangt tatsächlich so eine Dummy-Implementation, was mich auch erst überrascht hat.

Also würdest du auch sagen, wenn ich stackorientiert arbeiten will, führt weiterhin kein Weg an den direkten Header-Files vorbei?
Antworten