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

发布于 2024-08-18 10:09:28 字数 1916 浏览 2 评论 0原文

我在 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开发),以及它是否在现实世界中工作。


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

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 define 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

好听的两个字的网名 2024-08-25 10:09:28

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

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);
    /* ... */
}
时光暖心i 2024-08-25 10:09:28

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

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);
莫相离 2024-08-25 10:09:28

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

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

对于提供 bar() 和 baz() 的类将正确运行,而对于任何其他类则无法编译。

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.

魔法少女 2024-08-25 10:09:28

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文