代码之家  ›  专栏  ›  技术社区  ›  ggambetta

c++中编译时接口实现的检查

  •  5
  • ggambetta  · 技术社区  · 15 年前

    我在C++中使用伪接口,即纯抽象类。假设我有三个接口,IFoo、IBar和iquex。我还有一个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和iquex的不同方法,会发生什么?我试图声明一个新接口ibarandqux,并声明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<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开发),以及它是否在现实环境中工作。


    最后,我意识到我的方法可以接受一个指向其中一个接口的指针和一个接口的动态强制转换,以便在运行时强制使用正确的参数类型,但我更喜欢编译时解决方案。

    4 回复  |  直到 15 年前
        1
  •  7
  •   Georg Fritzsche    15 年前

    考虑先使用经过测试的解决方案- 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);
        /* ... */
    }
    
        2
  •  3
  •   Mike Seymour    15 年前

    要以面向对象的方式执行此操作,您需要虚拟继承来确保 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
    

    正如其他人所说,您可以做一些类似于第二次尝试使用模板获取静态多态性的事情。

    你尝试的角色是不可能的,例如 弗莱德 不是 IBarAndBaz . 强制强制转换将编译,因为无论转换是否安全,大多数强制转换都将编译,但在这种情况下,它将给出未定义的行为。

    编辑 :或者,如果您不想使用模板,也不喜欢定义所有可能的接口组的组合爆炸,则可以定义函数,将每个接口作为单独的参数:

    void doTest(IBar *bar, IBaz *baz)
    {
        bar->bar();
        baz->baz();
    }
    
    class Fred : public IBar, public IBaz {};
    
    Fred fred;
    doTest(&fred,&fred);
    
        3
  •  1
  •   moonshadow    15 年前

    使用模板元编程可以达到以下效果:

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

    对于提供bar()和baz()的类,将正常工作,并且无法为任何其他类编译。

        4
  •  1
  •   ReWrite    15 年前

    你所能做的就是用一个模板化的构造函数创建一个类,它接受一个任意的指针,使用隐式下推得到你想要的两个接口,然后实现这个组合接口。

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