¿Cuáles son las alternativas a este código de generación jerarquía de clases basadas en typelist?

StackOverflow https://stackoverflow.com/questions/4735500

Pregunta

Estoy trabajando con un modelo de objeto simple en el que los objetos se pueden implementar interfaces para proporcionar una funcionalidad opcional. En su corazón, un objeto tiene que implementar un método getInterface que se da un (único) ID de interfaz. Después, el método devuelve un puntero a una interfaz - o nula, en caso de que el objeto no implementa la interfaz requerida. He aquí un esbozo de código para ilustrar esto:

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

Para hacer las cosas más fácil para los clientes que trabajan en este marco, que estoy usando un poco de plantilla que genera automáticamente la aplicación 'GetInterface' para que los clientes sólo tienen que poner en práctica las funciones reales requeridas por las interfaces. La idea es obtener un tipo concreto de Object, así como todas las interfaces y luego dejar getInterface simplemente punteros volver a this (fundido al tipo correcto). Aquí está la plantilla y un uso de demostración:

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

Esto funciona bastante bien, pero hay dos problemas que son feas:

  1. Un bloqueador para mí es que esto no funciona con MSVC6 (que tiene poco apoyo para las plantillas, pero lamentablemente tengo que apoyarla). MSVC6 produce un error C1202 al compilar esto.

  2. Toda una gama de clases (una jerarquía lineal) es generado por la plantilla ObjectWithIface recursiva. Esto no es un problema para mí per se, pero por desgracia no se puede simplemente hacer una sola declaración switch para asignar un ID de interfaz a un puntero en getInterface. En cambio, cada paso en los controles jerarquía para una única interfaz y, a continuación reenvía la petición a la clase base.

¿Alguien tiene sugerencias de cómo mejorar esta situación? Ya sea mediante la fijación de las anteriores dos problemas con la plantilla ObjectWithIface, o sugiriendo alternativas que harían marco del Objeto / interfaz más fácil de usar.

¿Fue útil?

Solución

¿Qué pasa algo por el estilo?

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

Es posible que desee algo más elaborado que type_info const* si usted quiere hacer esto a través de fronteras bibliotecas dinámicas. Algo así como std::string y type_info::name() trabajará muy bien (aunque un poco lento, pero este tipo de expedición extrema probablemente necesitará algo lento). También puede fabricar identificadores numéricos, pero esto es tal vez más difícil de mantener.

El almacenamiento de hashes de type_infos es otra opción:

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

y el uso FooInterface::hash() cuando se agrega la interfaz y el Interface::type() virtual cuando consulta.

Otros consejos

dynamic_cast existe en el lenguaje para resolver este problema exacto.

Ejemplo de uso:

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 también se encargará de la perfección cosas como herencia múltiple y virtual, que bien puede luchar.

Editar:

Usted puede comprobar QueryInterface de COM para esto- que utilizan un diseño similar con una extensión compilador. Nunca he visto código COM implementado, sólo se utilizan los encabezados, pero se puede buscar a él.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top