开发者

Compile-time interface implementation check in C++

I'm using pseudo-interfaces in C++, that is, pure abstract classes. Suppose I have three interfaces, IFoo, IBar and IQuux. I also have a class Fred that implements all three of them:

interface IFoo
{
    void foo (void);
}   

interface IBar
{
    void bar (void);
}

interface IQuux
{
    void quux (void);
}   

class Fred : implements IFoo, IBar, IQuux
{
}

I want to declare a method that accepts any object that implements IFoo and IBar - a Fred would work, for example. The only compile-time way to do this I can imagine is to defi开发者_StackOverflowne a third interface IFooAndBar that implements both, and redeclare Fred:

interface IFooAndBar : extends IFoo, IBar
{   
}

class Fred : implements IFooAndBar, IQuux
{
}

Now I can declare my method as receiving a IFooAndBar*. So far so good.


However, what happens if I also want a different method that accepts IBar and IQuux? I tried declaring a new interface IBarAndQuux and declaring Fred as inheriting both :

class IFooAndBar : IFoo, IBar
{
};


class IBarAndQuux : IBar, IQuux
{
};


class Fred : IFooAndBar, IBarAndQuux
{
};

This works when I pass Fred as a IFooAndBar to a method; however, when I try to call Fred::bar() directly, gcc complains:

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

which makes this solution more or less useless.


My next attempt was to declare Fred as inheriting from the three individual interfaces, and making the method accept one of the hybrid interfaces as a parameter :

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

};

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

When I try to pass Fred as the IBarAndBaz* parameter, I get an error, as expected:

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

dynamic_cast<> also produces an error (which I don't understand)

error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic)

Forcing a cast does work, however :

doTest((IBarAndBaz*)pFred);

but I wonder how safe and portable this is (I develop for Linux, Mac and Windows), and whether it works in a real-world situation.


Finally, I realize my method can accept a pointer to one of the interfaces and dynamic_cast to the other(s) to enforce the correct parameter type at runtime, but I prefer a compile-time solution.


Consider using tested solutions first - Boost.TypeTraits to the rescue:

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);
    /* ... */
}


To do this in OO style, you need virtual inheritance to ensure that Fred only ends up with one copy of 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

As others have said, you can do something similar to your second attempt using templates to get static polymorphism.

The cast you were trying isn't possible, as an instance of Fred isn't an instance of IBarAndBaz. The forced cast compiles because most forced casts will compile, whether or not the conversion is safe, but in this case it will give undefined behaviour.

Edit: Alternatively, if you don't want to use templates and don't like the combinatorical explosion of defining all the possible groups of interfaces, you could define the functions to take each interface as a separate parameter:

void doTest(IBar *bar, IBaz *baz)
{
    bar->bar();
    baz->baz();
}

class Fred : public IBar, public IBaz {};

Fred fred;
doTest(&fred,&fred);


You can achieve the effect using template metaprogramming:

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

will behave correctly for classes that supply bar() and baz(), and fail to compile for any other classes.


What you can do is create a class with a templated constructor that accepts an arbitrary pointer, uses implicit downcasting to get the two interfaces you want, and then implements the combined interface.

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;
};

Then you write a function that accepts IFooAndBar* if both interfaces are required, and the caller can construct a FooAndBarCompositor on the stack that dispatches to the object of their choice. It looks like:

void testFooAndBar(IFooAndBar* pI) {}

void baz(Fred* pFred)
{
    FooAndBarCompositor fb(pFred);
    testFooAndBar(&fb);
}

This is not very general, and forces you to write dispatch functions in the compositor. Another approach is to have a generic interface compositor template:

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;
};

Then the function looks like:

void testFooAndBar(InterfaceCompositor<IFoo, IBar> pI)
{
    IFoo* pFoo = pI; // Or pI.AsA();
    IBar* pBar = pI; // Of pI.AsB();
}

This requires the function that wants to enforce the multiple interfaces to either use the compositor where an A* or B* is expected (e.g. assignment or function parameter) or explicitly call the appropriate AsX() method. Specifically, the interface to use cannot be inferred from the use of the -> operator and the * operator has no meaning on the composite.

If you go with the generic code, you can use the same template for enforcing that the object support both IBar and IBaz as well.

C++0x will introduce variadic templates that will allow this concept to be extended to arbitrary numbers of interface classes.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜