2 Stimmen

Abgleich von Klassen, Vererbung und C-Callbacks

In meinem C++-Projekt habe ich mich für die Verwendung einer C-Bibliothek entschieden. In meinem Eifer, ein gut abstrahiertes und einfaches Design zu haben, habe ich mich ein wenig verrannt. Ein Teil meiner Designanforderung besteht darin, dass ich problemlos mehrere APIs und Bibliotheken für eine bestimmte Aufgabe unterstützen kann (in erster Linie aufgrund meiner Anforderung nach plattformübergreifender Unterstützung). Also entschied ich mich, eine abstrakte Basisklasse zu erstellen, die eine bestimmte Auswahl an Bibliotheken einheitlich behandeln würde.

Betrachten Sie diese Vereinfachung meines Entwurfs:

class BaseClass
{
public:
    BaseClass() {}
    ~BaseClass() {}

    bool init() { return doInit(); }
    bool run() { return doWork(); }
    void shutdown() { destroy(); }
private:
    virtual bool doInit() = 0;
    virtual bool doWork() = 0;
    virtual void destroy() = 0;
};

Und eine Klasse, die von ihr erbt:

class LibrarySupportClass : public BaseClass
{
public:
    LibrarySupportClass()
        : BaseClass(), state_manager(new SomeOtherClass()) {}

    int callbackA(int a, int b);
private:
    virtual bool doInit();
    virtual bool doWork();
    virtual void destroy();

    SomeOtherClass* state_manager;
};

// LSC.cpp:

bool LibrarySupportClass::doInit()
{
    if (!libraryInit()) return false;

    // the issue is that I can't do this:
    libraryCallbackA(&LibrarySupportClass::callbackA);

    return true;
}
// ... and so on

Das Problem, auf das ich gestoßen bin, ist, dass, da es sich um eine C-Bibliothek handelt, ich einen C-kompatiblen Rückruf der Form bereitstellen muss int (*)(int, int) , aber die Bibliothek unterstützt keinen zusätzlichen Userdata-Zeiger für diese Callbacks. Ich würde lieber alle diese Rückrufe innerhalb der Klasse durchführen, da die Klasse ein Statusobjekt enthält.

Was ich letztendlich getan habe, ist...

static LibrarySupportClass* _inst_ptr = NULL;
static int callbackADispatch(int a, int b)
{
    _inst_ptr->callbackA(a, b);
}

bool LibrarySupportClass::doInit()
{
    _inst_ptr = this;

    if (!libraryInit()) return false;

    // the issue is that I can't do this:
    libraryCallbackA(&callbackADispatch);

    return true;
}

Dies wird eindeutig Bad Things(TM) tun, wenn LibrarySupportClass mehr als einmal instanziiert wird, so dass ich erwog, das Singleton-Design zu verwenden, aber aus diesem einen Grund kann ich diese Wahl nicht rechtfertigen.

Gibt es einen besseren Weg?

3voto

ChrisW Punkte 53239

Sie können diese Wahl rechtfertigen: Sie begründen dies damit, dass die C-Bibliothek nur eine Callback-Instanz unterstützt.

Singletons machen mir Angst: Es ist nicht klar, wie man ein Singleton korrekt zerstört, und die Vererbung verkompliziert die Sache nur. Ich werde mir diesen Ansatz noch einmal ansehen.

Ich würde es folgendermaßen machen.

LibrarySupportClass.h

class LibrarySupportClass : public BaseClass
{
public:
    LibrarySupportClass();
    ~LibrarySupportClass();
    static int static_callbackA(int a, int b);
    int callbackA(int a, int b);
private:
    //copy and assignment are rivate and not implemented
    LibrarySupportClass(const LibrarySupportClass&);
    LibrarySupportClass& operator=(const LibrarySupportClass&);
private:
    static LibrarySupportClass* singleton_instance;
};

LibrarySupportClass.cpp

LibrarySupportClass* LibrarySupportClass::singleton_instance = 0;

int LibrarySupportClass::static_callbackA(int a, int b)
{
  if (!singleton_instance)
  {
    WHAT? unexpected callback while no instance exists
  }
  else
  {
    return singleton_instance->callback(a, b);
  }
}

LibrarySupportClass::LibrarySupportClass()
{
  if (singleton_instance)
  {
    WHAT? unexpected creation of a second concurrent instance
    throw some kind of exception here
  }
  singleton_instance = this;
}

LibrarySupportClass::~LibrarySupportClass()
{
  singleton_instance = 0;
}

Ich will damit sagen, dass man ihm nicht die externe Schnittstelle eines kanonischen "Singletons" geben muss (was es z.B. schwierig macht, es zu zerstören).

Stattdessen kann die Tatsache, dass es nur ein Exemplar gibt, ein privates Implementierungsdetail sein und durch ein privates Implementierungsdetail erzwungen werden (z. B. durch die throw-Anweisung im Konstruktor) ... vorausgesetzt, der Anwendungscode ist bereits so beschaffen, dass er nicht versuchen wird, mehr als eine Instanz dieser Klasse zu erzeugen.

Eine API wie diese (anstelle der kanonischen "Singleton"-API) bedeutet, dass Sie zum Beispiel eine Instanz dieser Klasse auf dem Stack erstellen können, wenn Sie das wollen (vorausgesetzt, Sie versuchen nicht, mehr als eine davon zu erstellen).

3voto

Dani van der Meer Punkte 6069

Die externe Beschränkung der C-Bibliothek diktiert, dass Sie beim Aufruf Ihres Rückrufs nicht die Identifikation der "besitzenden" Instanz des Rückrufs haben. Daher denke ich, dass Ihr Ansatz richtig ist.

Ich würde vorschlagen, die callbackDispatch-Methode als statisches Mitglied der Klasse zu deklarieren und die Klasse selbst zu einem Singleton zu machen (es gibt viele Beispiele dafür, wie man ein Singleton implementiert). So können Sie ähnliche Klassen für andere Bibliotheken implementieren.

1voto

supercheetah Punkte 3072

Dani kam mir mit der Antwort zuvor, aber eine andere Idee ist, dass Sie ein Nachrichtensystem haben könnten, bei dem die Rückruffunktion die Ergebnisse an alle oder einige der Instanzen Ihrer Klasse sendet. Wenn es keinen sauberen Weg gibt, um herauszufinden, welche Instanz die Ergebnisse erhalten soll, dann lassen Sie die Instanzen, die sie nicht benötigen, die Ergebnisse einfach ignorieren.

Natürlich hat dies das Problem der Leistung, wenn Sie viele Instanzen haben, und Sie müssen durch die gesamte Liste zu iterieren.

1voto

Dimitri Tcaciuc Punkte 4523

Das Problem, wie ich es sehe, ist, dass, weil Ihre Methode nicht statisch ist, können Sie sehr leicht am Ende mit einem internen Zustand in einer Funktion, die nicht eine haben soll, die, weil es eine einzelne Instanz am Anfang der Datei, zwischen Aufrufen übertragen werden kann, die eine -wirklich- schlechte Sache (tm) ist. Zumindest, wie Dani oben vorschlug, müssten alle Methoden, die Sie von Ihrem C-Callback aus aufrufen, statisch sein, damit Sie sicherstellen, dass kein Restzustand von einem Aufruf Ihres Callbacks übrig bleibt.

Die obigen Ausführungen setzen voraus, dass Sie über static LibrarySupportClass* _inst_ptr ganz oben erklärt. Als Alternative können Sie eine Fabrikfunktion verwenden, die Arbeitskopien Ihrer LibrarySupportClass bei Bedarf aus einem Pool. Diese Kopien können dann in den Pool zurückkehren, wenn Sie sie nicht mehr benötigen, und wiederverwendet werden, so dass Sie nicht jedes Mal eine neue Instanz erstellen müssen, wenn Sie diese Funktion benötigen.

Auf diese Weise können Sie Ihre Objekte während eines einzelnen Callback-Aufrufs in einem Zustand halten, da es einen eindeutigen Punkt geben wird, an dem Ihre Instanz freigegeben wird und grünes Licht zur Wiederverwendung erhält. Sie werden auch in einer viel besseren Position für eine Multi-Thread-Umgebung sein, in dem Fall bekommt jeder Thread seine eigene LibrarySupportClass Instanz.

0voto

dirkgently Punkte 104289

Das Problem, auf das ich gestoßen bin, ist, dass, weil dies eine C-Bibliothek ist, ich einen C-kompatiblen Rückruf der Form int (*)(int, int) bereitstellen muss, aber die Bibliothek unterstützt keinen zusätzlichen Userdata-Zeiger für diese Rückrufe

Können Sie das näher erläutern? Ist die Auswahl eines Rückruftyps auf der Grundlage von Benutzerdaten ein Problem?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X