Каковы альтернативы этому коду генерации иерархии классов на основе списков типов? - PullRequest
3 голосов
/ 19 января 2011

Я работаю с простой объектной моделью, в которой объекты могут реализовывать интерфейсы для обеспечения дополнительных функций.По сути, объект должен реализовывать метод getInterface, которому присваивается (уникальный) идентификатор интерфейса.Затем метод возвращает указатель на интерфейс - или ноль, если объект не реализует запрошенный интерфейс.Вот эскиз кода, чтобы проиллюстрировать это:

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

Чтобы упростить работу для клиентов, работающих в этой среде, я использую небольшой шаблон, который автоматически генерирует реализацию getInterface, так что клиентам просто нужнореализовать фактические функции, требуемые интерфейсами.Идея состоит в том, чтобы извлечь конкретный тип из Object, а также из всех интерфейсов, а затем позволить getInterface просто вернуть указатели в this (приведенный к нужному типу).Вот шаблон и демонстрационное использование:

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

Это работает довольно хорошо, но есть две проблемы, которые уродливы:

  1. Блокировщик для меня заключается в том, что этоне работает с MSVC6 (который плохо поддерживает шаблоны, но, к сожалению, мне нужно его поддерживать).MSVC6 выдает ошибку C1202 при компиляции.

  2. Целый диапазон классов (линейная иерархия) генерируется рекурсивным шаблоном ObjectWithIface.Это само по себе не проблема, но, к сожалению, я не могу просто сделать один оператор switch, чтобы сопоставить идентификатор интерфейса с указателем в getInterface.Вместо этого каждый шаг в иерархии проверяет наличие единственного интерфейса и затем перенаправляет запрос в базовый класс.

У кого-нибудь есть предложения, как улучшить эту ситуацию?Либо исправив две вышеуказанные проблемы с шаблоном ObjectWithIface, либо предложив альтернативы, которые облегчили бы использование инфраструктуры объектов / интерфейсов.

Ответы [ 2 ]

3 голосов
/ 19 января 2011

dynamic_cast существует в языке для решения этой конкретной проблемы.

Пример использования:

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 также будет беспрепятственно обрабатывать такие вещи, как множественное и виртуальное наследование, которое выможет хорошо бороться с.

Редактировать:

Вы можете проверить QueryInterface COM для этого - они используют аналогичный дизайн с расширением компилятора.Я никогда не видел реализованного COM-кода, использовал только заголовки, но вы могли его искать.

2 голосов
/ 19 января 2011

Как насчет чего-то подобного?

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

Возможно, вы захотите что-то более сложное, чем type_info const*, если вы хотите сделать это за пределами динамических библиотек.Что-то вроде std::string и type_info::name() будет работать нормально (хотя и немного медленно, но для такого рода экстремальной отправки, вероятно, потребуется что-то медленное).Вы также можете создавать числовые идентификаторы, но это, возможно, сложнее поддерживать.

Еще одним вариантом является хранение хэшей type_infos:

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

и использование FooInterface::hash() при добавлении интерфейсавиртуальный Interface::type() при запросе.

...