C ++中的多重继承导致难以覆盖常见功能

时间:2009-01-11 01:46:08

标签: c++ inheritance class

在C ++物理模拟中,我有一个名为Circle和Square的类。这些是形状,并有一个名为push()的方法,它对它施加力。然后是Circle的一个特例,称之为SpecialCircle,其中push()应该表现出略微不同的属性。但事实上,还有SpecialSquare()应该具有相同的力属性。所以我想要一个名为Shape的抽象基类来处理Circles和Squares,但是我还想要一个名为Special的抽象基类,它将特殊属性应用于force()。

设计此类结构的最佳方法是什么?

到目前为止,我已经:

class Shape {
    virtual void push();
};

class Circle : public Shape {};

class Square : public Shape {};

class Special {
    virtual void push();
};

class SpecialCircle : public Circle, Special {};

class SpecialSquare : public Square, Special {};

当然,上面不会编译,因为Special :: push()和Shape :: push()冲突。我得到了“错误:请求成员'推送'是模棱两可的”,正如预期的那样。

如何重新组织我的类结构,以便Circle和Square可以相互共享某些属性,但是SpecialCircle和SpecialSquare仍然可以继承Shape,还可以从Special继承修改后的功能?

感谢。

ps。,这是钻石继承问题吗?

6 个答案:

答案 0 :(得分:9)

另一种解决方案(它可能适合您的需求,也可能不适合您的需求,具体取决于您的实施细节):

  • 拥有类Behavior,让NormalBehavior和SpecialBehavior继承它。
  • 让类成为Shape,让Square和Circle继承它。设Shape是一个聚合类型,带有一个Behavior成员(即你将一个Behavior对象传递给各种Shape构造函数)。换句话说,让一个Shape有一个行为。
  • 将形状行为的实际差异委派给行为层次结构的方法。

相反,您可以:

  • 拥有PhysicalObject类,让NormalObject和SpecialObject继承它;
  • 让类Shape,让Square和Circle继承它;
  • 让PhysicalObject具有Shape。

首选聚合继承。这是Bridge pattern的应用。这个策略在Square,SpecialSquare,Circle和SpecialCircle方面的优势在于明天你必须添加Rectangle,Hexagon等等,对于你添加的每个形状,你必须实现 two 类(重复的代码是邪恶的);在我看来,这是Bridge解决的真正问题。

答案 1 :(得分:3)

据说软件中的每个问题都可以通过添加额外的间接层来解决。

Herb Sutter有一篇关于如何解决问题的优秀文章:Multiple Inheritance - Part III

简而言之,您使用中间类来“重命名”虚拟函数。正如赫伯所说:

  

重命名虚拟功能

     

如果两个继承的函数具有不同的签名,则没有问题:我们将像往常一样独立地覆盖它们。因此,诀窍是以某种方式改变两个继承函数中至少一个的签名。

     

更改基类函数签名的方法是创建一个从基类派生的中间类,声明一个新的虚函数,并覆盖继承的版本以调用新函数

以下是使用您的课程的一个很长的例子:

class Shape {
public:
    virtual void push() = 0;
};

class Circle : public Shape 
{
public:
    void push() {
        printf( "Circle::push()\n");
    }
};

class Square : public Shape 
{
public:
    void push() {
        printf( "Square::push()\n");
    }
};

class Special {
public:
    virtual void push() = 0;
};


class Circle2: public Circle
{
public:
    virtual void pushCircle() = 0;
    void push() {
        pushCircle();
    }
};

class Square2: public Square
{
public:
    virtual void pushSquare() = 0;
    void push() {
        pushSquare();
    }
};


class Special2 : public Special
{
public:
    virtual void pushSpecial() = 0;
    void push() {
        pushSpecial();
    }
};



class SpecialCircle : public Circle2, public Special2 
{
public:
    void pushSpecial() {
        printf( "SpecialCircle::pushSpecial()\n");
    }
    void pushCircle() {
        printf( "SpecialCircle::pushCircle()\n");
    }

};

class SpecialSquare : public Square2, public Special2 
{
public:
    void pushSpecial() {
        printf( "SpecialSquare::pushSpecial()\n");
    }
    void pushSquare() {
        printf( "SpecialSquare::pushSquare()\n");
    }

};

int main( int argc, char* argv[])
{
    SpecialCircle sc;
    SpecialSquare ss;

    // sc.push();   // can't be called - ambiguous
    // ss.push();
    sc.pushCircle();
    ss.pushSquare();

    Circle* pCircle = ≻
    pCircle->push();

    Square* pSquare = &ss;
    pSquare->push();

    Special* pSpecial = ≻
    pSpecial->push();

    pSpecial = &ss;
    pSpecial->push();


    return 0;
}

答案 2 :(得分:2)

不是通过继承来考虑代码重用,而是使用mixins将为您提供所需的代码重用,而不会出现多重继承问题。

如果您不熟悉该技术,请在SO或Google上进行搜索。确保搜索“mixin”和“Curiously Recurring Template Pattern”。有很多很棒的文章可以帮助你入门。

答案 3 :(得分:1)

如果必须使用相同的方法从多个接口继承,编译器无法判断您要调用哪个接口,可以通过覆盖此类方法并调用所需的方法来解决此问题。

class SpecialCircle : public Circle, Special {
  public:
    virtual void push() { Special::push(); }
};
class SpecialSquare : public Square, Special {
  public:
    virtual void push() { Special::push(); }
};

但在这种情况下,我认为正确的OO方法是将Federico Ramponi建议的推送行为分解出来。

答案 4 :(得分:0)

来自Shape和SpecialCircle的SpecialShape和来自SpecialShape的SpecialSquare。

答案 5 :(得分:0)

那么,如果特殊圆和普通圆都可以施加力,并且特殊圆有另一种方法应用特殊力,为什么不有两个接口和两个方法?

struct Applicable {
    virtual ~Applicable() { }

    // if it applies force, better be explicit with naming it.
    virtual void applyForce() = 0;
};

struct SpecialApplicable {
    virtual ~SpecialApplicable() { }

    virtual void applySpecialForce() = 0;
};

struct Shape {
    virtual ~Shape() { }
    Size getSize();
    Point getPosition();
    // ...
};

struct Circle : Shape, Applicable {
    virtual void applyForce() { /* ... */ }
}

struct SpecialCircle : Circle, SpecialApplicable {
    virtual void applySpecialForce() { /* .... */ } 
};

如果同时存在一个特殊的和普通的apply方法(类的名称 - SpecialCircle - 建议)没有意义,那么为什么不这样做呢:

struct Circle : Shape, Applicable {
    virtual void applyForce() { /* ... */ }
}

struct SpecialCircle : Circle {
    // applies force, but specially
    virtual void applyForce() { /* .... */ } 
};

您也可以将applyForce放入Shape类。它还取决于使用这些类的环境。在任何情况下,你真正应该避免的是在两个基本类中出现两个基本格的相同方法。因为那不可避免会导致这种模棱两可的问题。 diamond 继承是在使用虚拟继承时。我相信stackoverflow上有其他好的答案可以解释这一点。它不适用于您的问题,因为出现歧义是因为该方法出现在两个不同类型的基类子对象中。 (它只解决了基类具有相同类型的情况。在这种情况下,它将合并基类,并且只包含一个基类子对象 - 由虚拟继承继承)