Frage

Ich arbeite mit einem einfachen Objektmodell, in dem Objekt Schnittstellen implementieren kann optionale Funktionalität bereitzustellen. Bei ihrem Herzen hat eine Aufgabe, ein getInterface Verfahren zu implementieren, die eine (einmalige) Schnittstelle ID gegeben ist. Die Methode gibt dann einen Zeiger auf eine Schnittstelle - oder null, falls das Objekt nicht die angeforderte Schnittstelle nicht implementiert. Hier ist ein Code Skizze dieses zu verdeutlichen:

struct Interface { };
struct FooInterface : public Interface { enum { Id = 1 }; virtual void doFoo() = 0; };
struct BarInterface : public Interface { enum { Id = 2 }; virtual void doBar() = 0; };
struct YoyoInterface : public Interface { enum { Id = 3 }; virtual void doYoyo() = 0; };

struct Object {
    virtual Interface *getInterface( int id ) { return 0; }
};

Um die Dinge einfacher für Kunden, die Arbeit in diesem Rahmen, ich bin ein wenig Schablone, die automatisch die ‚GetInterface‘ Implementierung erzeugt, so dass Kunden müssen nur die eigentlichen Funktionen der Schnittstellen erforderlich implementieren. Die Idee ist, einen konkreten Typ von Object sowie alle Schnittstellen leiten und dann lassen getInterface nur Zeiger auf this zurückzukehren (nach rechts Typ gecastet). Hier ist die Vorlage und eine Demo-Nutzung:

struct NullType { };
template <class T, class U>
struct TypeList {
    typedef T Head;
    typedef U Tail;
};

template <class Base, class IfaceList>
class ObjectWithIface :
    public ObjectWithIface<Base, typename IfaceList::Tail>,
    public IfaceList::Head
{
public:
    virtual Interface *getInterface( int id ) {
        if ( id == IfaceList::Head::Id ) {
            return static_cast<IfaceList::Head *>( this );
        }
        return ObjectWithIface<Base, IfaceList::Tail>::getInterface( id );
    }
};

template <class Base>
class ObjectWithIface<Base, NullType> : public Base
{
public:
    virtual Interface *getInterface( int id ) {
        return Base::getInterface( id );
    }
};

class MyObjectWithFooAndBar : public ObjectWithIface< Object, TypeList<FooInterface, TypeList<BarInterface, NullType> > >
{
public:
    // We get the getInterface() implementation for free from ObjectWithIface
    virtual void doFoo() { }
    virtual void doBar() { }
};

Das funktioniert ganz gut, aber es gibt zwei Probleme, die hässlich sind:

  1. für mich Ein Blocker ist, dass dies nicht funktioniert mit MSVC6 (die Vorlagen schlechte Unterstützung hat, aber leider muss ich es unterstützen). MSVC6 ergibt einen C1202 Fehler, wenn diese zu kompilieren.

  2. Eine ganze Reihe von Klassen (a linear Hierarchie) wird durch die rekursive ObjectWithIface Matrize erzeugt. Dies ist kein Problem für mich per se, aber leider kann ich nicht nur eine einzige switch Anweisung tun eine Schnittstelle ID auf einen Zeiger in getInterface abzubilden. Stattdessen jeder Schritt in der Hierarchie überprüft für eine einzelne Schnittstelle und leitet dann die Anforderung an die Basisklasse.

Hat jemand hat Vorschläge, wie diese Situation zu verbessern? Entweder durch die beiden oben genannten Probleme mit der ObjectWithIface Vorlage Festsetzung oder durch Alternativen vorschlagen, die das Objekt / Interface-Framework einfacher machen würde.

War es hilfreich?

Lösung

Was ist so etwas wie das?

struct Interface
{
    virtual ~Interface() {}
    virtual std::type_info const& type() = 0;
};

template <typename T>
class InterfaceImplementer : public virtual Interface 
{
    std::type_info const& type() { return typeid(T); }
};

struct FooInterface : InterfaceImplementer<FooInterface>
{
    virtual void foo();
};

struct BarInterface : InterfaceImplementer<BarInterface>
{
    virtual void bar();
};

struct InterfaceNotFound : std::exception {};

struct Object
{
    void addInterface(Interface *i)
    {
        // Add error handling if interface exists
        interfaces.insert(&i->type(), i);
    }

    template <typename I>
    I* queryInterface()
    {
        typedef std::map<std::type_info const*, Interface*>::iterator Iter;
        Iter i = interfaces.find(&typeid(I));
        if (i == interfaces.end())
            throw InterfaceNotFound();

        else return static_cast<I*>(i->second);
    }

private:
    std::map<std::type_info const*, Interface*> interfaces;
};

Sie können als type_info const* etwas mehr erarbeiten wollen, wenn Sie diese über dynamische Bibliotheken Grenzen tun wollen. So etwas wie std::string und type_info::name() wird funktionieren (wenn auch ein wenig langsam, aber diese Art von extremem Versand wird wahrscheinlich braucht etwas langsam). Sie können auch numerische IDs herstellen, aber das ist vielleicht schwieriger zu pflegen.

Hashes von type_infos Speicher ist eine weitere Option:

template <typename T>
struct InterfaceImplementer<T>
{
    std::string const& type(); // This returns a unique hash
    static std::string hash(); // This memoizes a unique hash
};

und Verwendung FooInterface::hash(), wenn Sie fügen Sie die Schnittstelle und die virtuelle Interface::type(), wenn Sie Abfrage.

Andere Tipps

dynamic_cast exists within the language to solve this exact problem.

Example usage:

class Interface { 
    virtual ~Interface() {} 
}; // Must have at least one virtual function
class X : public Interface {};
class Y : public Interface {};

void func(Interface* ptr) {
    if (Y* yptr = dynamic_cast<Y*>(ptr)) {
        // Returns a valid Y* if ptr is a Y, null otherwise
    }
    if (X* xptr = dynamic_cast<X*>(ptr)) {
        // same for X
    }
}

dynamic_cast will also seamlessly handle things like multiple and virtual inheritance, which you may well struggle with.

Edit:

You could check COM's QueryInterface for this- they use a similar design with a compiler extension. I've never seen COM code implemented, only used the headers, but you could search for it.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top