【问题标题】:Compile-time interface implementation check in C++C++ 中的编译时接口实现检查
【发布时间】:2011-01-06 11:06:19
【问题描述】:

我在 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 也会产生错误(我不明白)

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 以在运行时强制执行正确的参数类型,但我更喜欢编译时解决方案。

【问题讨论】:

  • 它不安全,也不便携。事实上,它在 C++ 中是未定义的。

标签: c++ inheritance interface multiple-inheritance


【解决方案1】:

考虑首先使用经过测试的解决方案 - 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);
    /* ... */
}

【讨论】:

  • 在 boost 之外还有一个世界 :-)
  • 当然,bcp 对此有帮助;)但是说真的,为什么要复制如此全面的编译器解决方法集合,尤其是对于类型特征和类似的?
  • 如果普通的C++(虚拟继承)能解决问题,我认为拿出大炮不是个好主意。
  • 我不认为类型特征等是一把大枪,它们是普通业务的好工具。在我看来,他们在这里也更清楚地传达了意图。
【解决方案2】:

要以 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);

【讨论】:

  • 这可行。不幸的是,我必须在 Fred 的定义中声明所有可能的接口组合(至少是我使用的那些)......如果它可以在用户方法位置而不是在参数位置解决会更好,就像基于模板的解决方案一样。
【解决方案3】:

您可以使用模板元编程来实现效果:

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

对于提供 bar() 和 baz() 的类将表现正确,并且对于任何其他类都无法编译。

【讨论】:

  • 这很有趣。实际上,我的真实案例更复杂——我有一个类,它接收需要实现两个接口的对象,保留一个指向它的指针,然后调用它的方法。我不习惯将那个大类作为模板实现:(
  • 可以编写一个类型提供特定成员函数而不调用该函数的编译时断言。您可以在模板化包装器中执行此操作,然后调用实际函数,模板化代码使用 inline 属性声明,因此它可以编译掉。然而,实现是多毛的 - 使用 boost 中的一个(参见 gf 的答案)比自己滚动更容易。
【解决方案4】:

您可以做的是创建一个带有模板构造函数的类,该构造函数接受任意指针,使用隐式向下转换来获得您想要的两个接口,然后实现组合接口。

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 将引入可变参数模板,允许将此概念扩展到任意数量的接口类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-13
    • 2012-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 1970-01-01
    • 2021-10-06
    相关资源
    最近更新 更多