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

Posted

技术标签:

【中文标题】C++ 中的多重继承导致难以覆盖通用功能【英文标题】:Multiple inheritance in C++ leading to difficulty overriding common functionality 【发布时间】:2009-01-11 01:46:07 【问题描述】:

在 C++ 物理模拟中,我有一个名为 Circle 和 Square 的类。这些是 Shapes,并且有一个名为 push() 的方法,可以对其施加力。然后是 Circle 的一个特殊情况,称为 SpecialCircle,其中 push() 应该表现出稍微不同的属性。但实际上,还有 SpecialSquare() 应该表现出相同的力属性。所以我想要一个名为 Shape 的抽象基类来处理圆形和正方形,但我也想要一个名为 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.这是钻石继承问题吗?

【问题讨论】:

这不是菱形问题 - 那是当您从 2 个类继承时,每个类也从一个类继承。所以派生类有 2 条通往最终基类的路径。 "类广场:公共广场;"错字? 【参考方案1】:

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

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

相反,您可以:

拥有PhysicalObject类,并让NormalObject和SpecialObject继承它; 拥有Shape类,让Square和Circle继承它; 让 PhysicalObject 有一个 Shape。

更喜欢聚合而不是继承。这是Bridge pattern 的一个应用程序。这种策略相对于 Square、SpecialSquare、Circle 和 SpecialCircle 的优势在于,明天您必须添加 Rectangle、Hexagon 等,并且对于您添加的每个形状,您都必须实现 两个 类(重复的代码是邪恶的);在我看来,这是 Bridge 解决的真正问题。

【讨论】:

【参考方案2】:

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

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;

【讨论】:

【参考方案3】:

与其想通过继承来重用代码,使用mixins会给你你想要的代码重用,没有多重继承的问题。

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

【讨论】:

【参考方案4】:

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

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 所建议的那样,在其自己的类中排除推送行为。

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

好吧,如果特殊圆和普通圆都可以施力,而特殊圆还有另一种施加特殊力的方法,那为什么不能有两个接口和两个方法呢?

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 继承是当您使用虚拟继承时。我相信在 *** 上还有其他很好的答案可以解释这一点。它不适用于您的问题,因为该方法出现在两个不同类型的基类子对象中会产生歧义。 (它只解决基类具有相同类型的情况。在这种情况下,它将合并基类,并且只包含一个基类子对象-通过虚拟继承继承)

【讨论】:

以上是关于C++ 中的多重继承导致难以覆盖通用功能的主要内容,如果未能解决你的问题,请参考以下文章

C++多重继承与void*指针转换问题

适配器模式(C++)多重继承和组合实现

CDI:由于多重继承和泛型抽象导致的属性注入问题

django中的Python多重继承函数覆盖和ListView

Extjs 多重继承?

C++反汇编第四讲,认识多重继承,菱形继承的内存结构,以及反汇编中的表现形式.