2010-01-18 83 views
5

我在C++中使用伪接口,即纯抽象类。假设我有三个接口,IFoo,IBar和IQuux。我也有一个Fred类实现所有他们三个:C++编译时接口实现检查

interface IFoo 
{ 
    void foo (void); 
} 

interface IBar 
{ 
    void bar (void); 
} 

interface IQuux 
{ 
    void quux (void); 
} 

class Fred : implements IFoo, IBar, IQuux 
{ 
} 

我想声明的是接受实现的IFoo和伊巴尔任何对象的方法 - 一弗雷德会的工作,例如。唯一的编译时间的方式来做到这一点,我能想象是定义一个实现这两个第三接口IFooAndBar,并再次声明弗雷德:

interface IFooAndBar : extends IFoo, IBar 
{ 
} 

class Fred : implements IFooAndBar, IQuux 
{ 
} 

现在我可以宣布我的方法接收IFooAndBar *。到现在为止还挺好。


但是,会发生什么,如果我也想接受伊巴尔和IQuux不同的方法?我试图宣布一个新的接口IBarAndQuux并宣布弗雷德继承两个:

class IFooAndBar : IFoo, IBar 
{ 
}; 


class IBarAndQuux : IBar, IQuux 
{ 
}; 


class Fred : IFooAndBar, IBarAndQuux 
{ 
}; 

这工作当我通过弗雷德作为IFooAndBar的方法;然而,当我尝试叫弗雷德:: bar()的直接,GCC抱怨:

error: request for member ‘bar’ is ambiguous 
error: candidates are: void IBar::bar() 
error:     void IBar::bar() 

,这使得该解决方案或多或少没用。


我的下一个尝试是宣布弗雷德从三个单独的接口继承,并且使得该方法接受混合接口作为参数之一:

class Fred : public IFoo, public IBar, public IBaz 
{ 

}; 

void doTest (IBarAndBaz* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

当我试图通过弗雷德作为IBarAndBaz *参数,我得到一个错误,如预期:

error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’ 

的dynamic_cast <>也p roduces一个错误(我不明白)

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的其他(一个或多个)执行在运行时正确的参数的类型,但我更喜欢一个编译时间的解决方案。

+2

这不安全,它是可移植的。事实上,它在C++中是未定义的。 – 2010-01-18 17:09:46

回答

6

首先考虑使用测试的解决方案 - Boost.TypeTraits救援:

tempate<class C> 
void doTest(C* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

将正确的行为对于供电杆()和巴兹类:

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

有一个世界以外的推动:-) – mmmmmmmm 2010-01-18 19:08:22

+0

当然,bcp有帮助;)但严重的是,为什么重复这样一个全面的编译器变通的集合,特别是类型特征和类似? – 2010-01-18 19:13:14

+1

如果正常的C++(虚拟继承)可以解决这个问题,我不认为这是一个好主意。 – mmmmmmmm 2010-01-18 19:42:20

1

可以使用模板元编程实现的效果(),并且无法编译任何其他类。

+0

这非常有趣。我的实际案例实际上比较复杂 - 我有一个类,它接收需要实现这两个接口的对象,保留一个指向它的对象,然后调用它的方法。我不习惯将这个大类作为模板来实现:( – ggambett 2010-01-18 17:26:52

+0

可以编译一个编译时断言,即一个类型提供一个特定的成员函数而不用调用该函数,你可以在模板包装器中这样做,然后调用真正的函数,模板化的代码被声明为inline属性,所以它会被编译掉,但是实现过程非常繁琐 - 使用boost的一个更容易(请参阅gf的答案),而不是自己编写。 – moonshadow 2010-01-18 19:13:11

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); 
+0

这是可行的。我必须在Fred的定义中声明所有可能的接口组合(至少是我使用的接口)......如果可以在用户方法位置解析,而不是在参数位置解析会更好,如基于模板的解决方案。 – ggambett 2010-01-18 17:38:38

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()方法。具体而言,无法使用 - >运算符推断使用的接口,并且*运算符对组合没有意义。

如果使用通用代码,则可以使用相同的模板来强制对象同时支持IBar和IBaz。

C++ 0x将引入可变参数模板,该参数将允许将此概念扩展到任意数量的接口类。