C++多重继承转换

15

我有一个类:

class Base;

此外,我还有一个接口。

class Interface;

接下来,我要创建一个类。

class Derived : public Base, public Interface;
如果我有`Base *object = new Derived;`,当然如果我知道对象实际上是从派生类继承而来的,我如何将`object`转换为`Interface`? 编辑: 我已经尝试了dynamic_cast和static_cast(未编译)。 让我更详细地解释问题:
我有:
class Object {...}

class ITouchResponder
{
public:
    virtual bool onTouchBegan(XTouch *touch) = 0;
    virtual void onTouchMoved(XTouch *touch) = 0;
    virtual void onTouchEnded(XTouch *touch) = 0;
};

class Ball : public Object, public ITouchResponder {...};

class TentacleSensor : public Object, public ITouchResponder {...}

对象有一个 bool touchable_ 属性。如果它为真,则表示该对象正在实现 ITouchResponder 接口。

当我使用它时:

bool Level::onTouchBegan(XTouch *touch)
{
    ...
    ITouchResponder *responder = callback.nearestTouchable();
    if (responder)
    {
        if (responder->onTouchBegan(touch))
        {
            if (responder != ball_)
            {
                touch->setUserData(responder);
            }
        }
    }

    return true;
}

ITouchResponder *QueryCallback::nearestTouchable() const
{
    for (list<Object*>::const_iterator it = objects_.begin(); it != objects_.end(); ++it)
    {
        if ( (*it)->isTouchable() ) return (*it)->asTouchResponder();
    }
    return 0;
}

asTouchResponderObject的一个方法:

    ITouchResponder * Object::asTouchResponder()
    {
        assert(touchable_);
        ITouchResponder *res = dynamic_cast<ITouchResponder*>(this);
        assert(res);
        return res;
    }

我在Xcode中遇到了严重的访问错误。

但是如果我将Object : public ITouchResponder 进行修改,一切都可以正常工作。我做错了什么?

完整的对象类:

class Object// : public ITouchResponder
{
public:
    struct Def
    {
        Def()
        {
            level = 0;
            world = 0;
            touchable = false;
            acceptsContacts = false;
            body = 0;
            node = 0;
        }
        Level *level;
        b2World *world;
        bool touchable;
        bool acceptsContacts;
        b2Body *body;
        XNode *node;
    };

    Object(const Def &def);
    virtual ~Object();

    virtual void update(float dt);
    bool isTouchable() const {return touchable_;}

    void addDependantObject(Object *object);
    void removeDependantObject(Object *object);

    virtual void objectWillBeRemoved(Object *object) {} //this function is automatically called to every dependant object when object is removed

    virtual XVec2 position() const;
    virtual float rotation() const;

    bool acceptsContacts() const {return acceptsContacts_;}

    b2Body *body() const {return body_;}

    Level *level() const {return level_;}
    b2World *world() const {return world_;}

    ITouchResponder *asTouchResponder();
    /*
    virtual bool onTouchBegan(XTouch *touch) {
        return false;
    }
    virtual void onTouchMoved(XTouch *touch)
    {
    }
    virtual void onTouchEnded(XTouch *touch) 
    {
    }*/

protected:
    Level *level_;
    b2World *world_;
    bool touchable_;
    bool acceptsContacts_;

    XNode *node_;
    b2Body *body_;

    list<Object*> dependantObjects_;
};

2
@vivek 在 C++ 代码中不要使用 C 式转换。新语法的引入是有原因的。 - RedX
理想情况下,您的纯虚拟公共接口可以通过在接口指针上调用在派生类中被覆盖以执行适当操作的方法而无需进行转换。 - AJG85
4个回答

9
如果 Basevirtual 函数(甚至是 virtual 析构函数),那么:
Derived *pDerived = dynamic_cast<Derived *>(object);

否则,请使用。
Derived *pDerived = static_cast<Derived *>(object);

请注意,如果“Base”没有虚函数,则“dynamic_cast”将无法编译。在“dynamic_cast”中,只有源必须是多态对象才能编译,如果目标不是多态的,则dynamic_cast将返回空指针:
假设“A”和“B”是多态类型,“C”是非多态类型,则
A *pA = dynamic_cast<A*>(new C()); //error - source is not polymorphic!

A *pA = dynamic_cast<A*>(new B()); //ok
if ( pA == 0 )
      cout << "pA will be null if B is not derived from A" << endl;

C *pC = dynamic_cast<C*>(new B()); //ok
if ( pC == 0 )
       cout << "pC must be null" << endl;

如果您能明确指出dynamic_cast可能返回空指针,那就更好了。此外,由于您的帖子主要改进之处在于澄清限制,因此值得注意的是,跨越虚继承关系需要使用dynamic_cast(而不能使用static_cast)。 - Matthieu M.

8

如果你确定对象是Derived类,使用static_cast进行转换;否则使用dynamic_cast并检查结果。


1
如果基类没有虚函数,dynamic_cast 将无法编译。 - Nawaz
2
@Nawaz:没错,但编译器会输出一个友好的错误信息,而 static_cast 如果对象类型不正确则会导致 UB,而编译器则不会有任何提示。因此,关键在于不需要虚函数,而是要知道对象的类型。 - sharptooth

4
您可以使用dynamic_cast<Derived*>(object),如果转换成功,则会返回Derived*;否则,转换将返回NULL。如果Base是多态类型,即包含虚函数,则使用此类型的转换,否则可以使用static_cast<Derived*>
问题在于您的Object类没有从接口ITouchResponder继承,因此您从Object到接口的dynamic_cast无效。只能在相互继承的类之间进行动态转换,这就是多态性的整个目的,所以,正如您在示例中建议的那样,您的Object类应该公开继承您的接口。
本质上,您正在执行一个向上转型,从Derived到Base Interface的ITouchResponder *res = dynamic_cast<ITouchResponder*>(this);,但是您的Derived实际上并没有从您的接口派生,所以它就不起作用了。

3
只有在基类是多态的情况下才能起作用(例如通过具有虚函数)。当你完全确定时,使用static_cast是可以的,但你应该想知道为什么你的确定不是通过直接使用类型来表达在代码中。或者通过使用虚函数或访问者模式来实现。 - PlasmaHH
1
他说他知道这是一个派生类,因此应该使用static_cast<Derived>(object),因为它会产生更少的开销。 - RedX
请展示您的“Object”基类,它里面是否有虚函数? - Tony The Lion
@Tony:为什么我不能使用Object obj = new Ball()并将其转换为ITouchResponder?Ball是ITouchResponder。 - Andrew

3
有几种方法,但它们的影响并不相同:
Derived* derived = static_cast<Derived*>(object);

如果您在编译时知道它应该是正确类型,请使用此选项。如果不可能,它不会在运行时失败,但会在编译时失败。

Derived* derived = dynamic_cast<Derived*>(object);

如果您不确定并希望运行时自动检查是否可能,请使用此选项。如果是,则会得到有效的指针;否则,您将得到nullptr。请注意,dynamic_cast<>检查在时间性能方面成本很高,因此许多人有时会使用具有type_info指针作为键的关联容器(如果可能),因为检查成本较低,但实际上这取决于上下文。要获取更多信息,请查找typeid关键字。

现在,也有一种C方式来做这件事,但不建议使用,因为不清楚编译器生成了什么。至少使用前面的方法,您可以确切地知道代码应该如何工作。所以我不会进行描述。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接