我正在使用一个简单的对象模型,其中对象可以实现接口以提供可选功能。在它的核心,一个对象必须实现一个getInterface
方法,该方法被赋予(唯一的)接口ID。然后,该方法返回指向接口的指针 - 如果对象未实现所请求的接口,则返回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
语句将接口ID映射到getInterface
中的指针。相反,层次结构中的每个步骤都会检查单个接口,然后将请求转发给基类。
有人建议如何改善这种情况吗?通过使用ObjectWithIface
模板解决上述两个问题,或者通过建议使对象/接口框架更易于使用的替代方法。
答案 0 :(得分:3)
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代码,只使用了标题,但你可以搜索它。
答案 1 :(得分:2)
那样的事情呢?
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()
之类的东西可以正常工作(虽然有点慢,但这种极端调度可能需要慢一点)。您也可以制作数字ID,但这可能更难维护。
存储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()
。