Каковы альтернативы этому коду генерации иерархии классов на основе типов?
-
12-10-2019 - |
Вопрос
Я работаю с простой объектной моделью, в которой объекты могут реализовать интерфейсы, чтобы обеспечить дополнительную функциональность. В этом сердце объект должен внедрить getInterface
Метод, который дается (уникальный) идентификатор интерфейса. Затем метод возвращает указатель на интерфейс - или NULL, если объект не реализует запрошенный интерфейс. Вот эскиз кода, чтобы проиллюстрировать это:
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() { }
};
Это работает довольно хорошо, но есть две проблемы, которые уродливы:
Блокатель для меня заключается в том, что это не работает с MSVC6 (который имеет плохую поддержку шаблонов, но, к сожалению, мне нужно поддерживать его). MSVC6 дает ошибку C1202 при составлении этого.
Целый диапазон классов (линейная иерархия) генерируется рекурсивным
ObjectWithIface
шаблон. Для меня это не проблема, но, к сожалению, я не могу просто сделать ни одногоswitch
оператор для отображения идентификатора интерфейса с указателем вgetInterface
. Анкет Вместо этого каждый шаг в иерархии проверяет один интерфейс, а затем пересылает запрос в базовый класс.
У кого -нибудь есть предложения, как улучшить эту ситуацию? Либо путем исправления двух вышеупомянутых проблем с ObjectWithIface
Шаблон или предложение альтернатив, которые облегчат использование объекта/интерфейса.
Решение
Как насчет чего -то подобного?
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()
Когда вы запросите.
Другие советы
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
также будет плавно обрабатывать такие вещи, как множественное и виртуальное наследство, с которым вы можете бороться.
Редактировать:
Вы можете проверить Com QueryInterface для этого- они используют аналогичный дизайн с расширением компилятора. Я никогда не видел, чтобы Com Code реализован, только использовал заголовки, но вы могли бы искать его.