我在C ++中使用伪接口,即纯抽象类。假设我有三个接口,IFoo,IBar和IQuux。我还有一个Fred类,它实现了所有这三个:
interface IFoo
{
void foo (void);
}
interface IBar
{
void bar (void);
}
interface IQuux
{
void quux (void);
}
class Fred : implements IFoo, IBar, IQuux
{
}
我想声明一个接受任何实现IFoo和IBar的对象的方法 - 例如Fred会工作。我能想象的唯一编译时这样做的方法是定义一个实现它们的第三个接口IFooAndBar,并重新声明Fred:
interface IFooAndBar : extends IFoo, IBar
{
}
class Fred : implements IFooAndBar, IQuux
{
}
现在我可以将我的方法声明为接收IFooAndBar *。到目前为止一切都很好。
<小时/>
然而,如果我还想要一种接受IBar和IQuux的不同方法,会发生什么?我尝试声明一个新的接口IBarAndQuux并声明Fred继承两者:
class IFooAndBar : IFoo, IBar
{
};
class IBarAndQuux : IBar, IQuux
{
};
class Fred : IFooAndBar, IBarAndQuux
{
};
当我将Fred作为IFooAndBar传递给方法时,这是有效的;但是,当我尝试直接调用Fred :: bar()时,gcc会抱怨:
error: request for member ‘bar’ is ambiguous
error: candidates are: void IBar::bar()
error: void IBar::bar()
这使得该解决方案或多或少无用。
<小时/>
我的下一次尝试是将Fred声明为继承三个单独的接口,并使该方法接受其中一个混合接口作为参数:
class Fred : public IFoo, public IBar, public IBaz
{
};
void doTest (IBarAndBaz* pObj)
{
pObj->bar();
pObj->baz();
}
当我尝试将Fred作为IBarAndBaz *参数传递时,我收到错误,如预期的那样:
error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’
的dynamic_cast&LT;&GT;也会产生错误(我不明白)
error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic)
强制演员 但是:
doTest((IBarAndBaz*)pFred);
但我想知道这是多么安全和便携(我为Linux,Mac和Windows开发),以及它是否适用于真实世界。
<小时/>
最后,我意识到我的方法可以接受指向其中一个接口的指针,而dynamic_cast接受其他接口以在运行时强制执行正确的参数类型,但我更喜欢编译时解决方案。
答案 0 :(得分:7)
首先考虑使用经过测试的解决方案 - Boost.TypeTraits来救援:
template<class T>
void takeFooAndBar(const T& t) {
BOOST_STATIC_ASSERT(
boost::is_base_of<IFoo, T>::value
&& boost::is_base_of<IBar, T>::value);
/* ... */
}
答案 1 :(得分:3)
要在OO风格中执行此操作,您需要虚拟继承以确保Fred
仅以IBar
的一个副本结束:
class IFooAndBar : public IFoo, public virtual IBar {};
class IBarAndQuux : public virtual IBar, public IQuux {};
class Fred : public IFooAndBar, public IBarAndQuux {};
Fred fred;
fred.bar(); // unambiguous due to virtual inheritence
正如其他人所说的,你可以做类似于第二次尝试使用模板来获取静态多态性。
您正在尝试的演员阵容是不可能的,因为Fred
的实例不是IBarAndBaz
的实例。强制转换编译是因为大多数强制转换都会编译,无论转换是否安全,但在这种情况下它会给出未定义的行为。
编辑:或者,如果您不想使用模板并且不喜欢定义所有可能的接口组的组合爆炸,您可以定义函数以将每个接口作为一个单独的参数:
void doTest(IBar *bar, IBaz *baz)
{
bar->bar();
baz->baz();
}
class Fred : public IBar, public IBaz {};
Fred fred;
doTest(&fred,&fred);
答案 2 :(得分:1)
您可以使用模板元编程实现效果:
tempate<class C>
void doTest( C* pObj )
{
pObj->bar();
pObj->baz();
}
对于提供bar()和baz()的类,将正常运行,并且无法为任何其他类编译。
答案 3 :(得分:1)
你可以做的是创建一个带有模板化构造函数的类,它接受一个任意指针,使用隐式向下转换来获得你想要的两个接口,然后实现组合接口。
struct IFoo
{
virtual void foo() = 0;
};
struct IBar
{
virtual void bar() = 0;
};
struct IFooAndBar : public IFoo, public IBar {};
class FooAndBarCompositor : public IFooAndBar
{
public:
template <class T>
FooAndBarCompositor(T* pImpl) : m_pFoo(pImpl), m_pBar(pImpl) {}
void foo() {m_pFoo->foo();}
void bar() {m_pBar->bar();}
private:
IFoo* m_pFoo;
IBar* m_pBar;
};
然后编写一个接受IFooAndBar *的函数(如果两个接口都是必需的),并且调用者可以在堆栈上构造一个FooAndBarCompositor,并调度到他们选择的对象。它看起来像:
void testFooAndBar(IFooAndBar* pI) {}
void baz(Fred* pFred)
{
FooAndBarCompositor fb(pFred);
testFooAndBar(&fb);
}
这不是很通用,并强制您在合成器中编写调度函数。另一种方法是使用通用接口合成器模板:
template <class IA, class IB>
class InterfaceCompositor
{
public:
template <class T>
InterfaceCompositor(T* pObj) : m_pIA(pObj), m_pIB(pObj) {}
IA* AsA() const {return m_pIA;}
operator IA* () const {return AsA();}
IB* AsB() cosnt {return m_pIB;}
operator IB* () const {return AsB();}
private:
IA* m_pIA;
IB* m_pIB;
};
然后该函数看起来像:
void testFooAndBar(InterfaceCompositor<IFoo, IBar> pI)
{
IFoo* pFoo = pI; // Or pI.AsA();
IBar* pBar = pI; // Of pI.AsB();
}
这要求希望强制执行多个接口的函数使用合成器,其中需要A *或B *(例如赋值或函数参数)或显式调用适当的AsX()方法。具体而言,无法使用 - &gt;来推断使用的界面。运算符和*运算符对复合词没有意义。
如果您使用通用代码,您可以使用相同的模板来强制该对象同时支持IBar和IBaz。
C ++ 0x将引入可变参数模板,允许将此概念扩展到任意数量的接口类。