Domanda

Sto lavorando con un modello semplice oggetto in cui gli oggetti possono implementare interfacce per fornire funzionalità opzionale. Al suo cuore, un oggetto deve implementare un metodo getInterface che è dato un (unico) ID di interfaccia. Il metodo restituisce un puntatore a un'interfaccia - o null, nel caso in cui l'oggetto non implementa l'interfaccia richiesta. Ecco uno schizzo di codice per illustrare questo:

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

Per rendere le cose più facili per i clienti che lavoro in questo quadro, sto usando un piccolo modello che genera automaticamente l'implementazione 'GetInterface' in modo che i clienti devono solo implementare le funzioni effettive richieste dalle interfacce. L'idea è di ricavare un tipo concreto da Object così come tutte le interfacce e poi lasciare getInterface solo tornare puntatori a this (fuso al tipo di destra). Ecco il modello e un uso demo:

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

Questo funziona abbastanza bene, ma ci sono due problemi che sono brutti:

  1. Un bloccante per me è che questo non funziona con MSVC6 (che ha scarso supporto per i template, ma purtroppo ho bisogno di sostenerlo). MSVC6 produce un errore C1202 quando si compila questo.

  2. Una vasta gamma di classi (una gerarchia lineare) è generato dal modello ObjectWithIface ricorsiva. Questo non è un problema per me per sé, ma purtroppo non posso fare solo una singola istruzione switch per mappare un ID di interfaccia a un puntatore in getInterface. Invece, ogni passo nella gerarchia dei controlli per una singola interfaccia e poi inoltra la richiesta alla classe di base.

Qualcuno ha suggerimenti su come migliorare questa situazione? Sia fissando questi due problemi con il modello ObjectWithIface, o proponendo alternative che renderebbero l'oggetto quadro / interfaccia più facile da usare.

È stato utile?

Soluzione

Che dire di una cosa del genere?

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

Si può volere qualcosa di più elaborato di type_info const* se si vuole fare questo attraverso biblioteche confini dinamici. Qualcosa di simile std::string e type_info::name() funzionerà bene (anche se un po 'lento, ma questo tipo di estrema spedizione sarà probabilmente bisogno di qualcosa di lento). È inoltre in grado di produrre ID numerici, ma questo è forse più difficile da mantenere.

Memorizzazione di hash di type_infos è un'altra opzione:

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

e l'uso FooInterface::hash() quando si aggiunge l'interfaccia, e il Interface::type() virtuale quando si query.

Altri suggerimenti

dynamic_cast esiste all'interno del linguaggio per risolvere questo problema esatto.

Esempio di utilizzo:

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 anche senza soluzione di continuità di gestire le cose come multipla e virtuale eredità, che si può ben lottare con.

Modifica:

Si potrebbe verificare QueryInterface di COM per questo- usano un design simile con estensione del compilatore. Non ho mai visto il codice COM implementato, usato solo le intestazioni, ma si potrebbe cercare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top