【问题标题】:multiple inheritance and data members多重继承和数据成员
【发布时间】:2011-06-25 08:14:36
【问题描述】:

我从未使用过多重继承,并偶然发现了一个我从未遇到过的设计问题..

class A {
     //..methods..
}

class B : public A {
    int b;
    //...methods..
}

class C : public A {
    int c1,c2;
}

class D : public B,public C {
}

这是经典的钻石。事实上,C 实际上只是一个带有两个额外整数的AD 实际上只是 BC 的聚合,但我觉得多重继承并不是为了制造这样的东西而设计的。或者可能有其他最佳做法可以做到这一点。

我尝试实现多重继承的原因是我想编写一个类似void func(A*) 的函数并将其传递给AD 类指针。我的天真的尝试是做一个简单的演员表:

void func(A* a) { // <-- I call this with a A or D pointer
   // ..do something with A members..

    if(is_the_case) { // <-- Im sure the passed "a" pointer is actually a *D
        D* d = (D*)a;
       // ..do something with the extra 2 ints provided by the C class..
    } 
}

不起作用.. 编译得很好,但是当 if(is_the_case) 被执行时我有一个非常奇怪的行为,清除了 2 个额外的整数 c1c2,也清除了 b(由 B 继承)。

我想起了钻石问题,但这里的层次结构中只有一个B(和两个A),所以我不明白为什么b 也会被清除。为了尝试,我在BC 声明中使用了公共虚拟。现在每次转换都是编译错误,除非我使用dynamic_cast..

有人能说清楚幕后发生的事情吗?考虑到还有其他课程,例如:

class E : public A {
    int e;
    //..methods..
}

class F : public E,public C {
}

也就是说,其他类只是从 A 派生的类的聚合 + 由 C 继承的两个额外整数,并且可以传递给采用 *A 的函数

谢谢,我尽量说清楚了..

【问题讨论】:

  • 将一个指针类型转换为另一个指针类型不会产生错误。你能展示你在D* d = (D*)a;之后做了什么吗? “清除c1,c2”是什么意思?你是怎么做到的?
  • c1=c2=0; ..现在 d.b 也是 0.. 我没有收到任何错误.. 它编译并运行.. 问题是 b 也设置为 0
  • 您能否在分配给 d.c1 和 d.c2 之前检查 d.b 值?可能已经是 0...
  • 当你用一个指向 D 的指针调用 func 时,你如何处理调用点的模棱两可的 base A?
  • 它应该抱怨。你确定层次结构吗?

标签: c++ oop multiple-inheritance


【解决方案1】:

您的代码可以正常工作,因为您使用了 C 风格的转换,可以是 reinterpret_cast,我的理解是您可以在任何两种指针类型之间使用 reinterpret_cast,即使它没有意义。从多重继承的基类转换为更派生的类时,您必须使用dynamic_caststatic_cast 会产生编译时错误。事实上,dynamic_cast 同时完成了这两项工作。

void func(A* a) { // <-- I call this with a A or D pointer
   // ..do something with A members..

    if(D* d = dynamic_cast<D*>(a)) { // a definitely points to a D
        // and we got a guaranteed good pointer too
    } 
}

这是一个很好的例子,说明了为什么应该避免 C 风格的强制转换。

【讨论】:

  • 但我在某处读到 dynamic_cast 会减慢执行速度。在这种情况下,我对性能非常敏感。有人可以确认吗?
  • @user815129:谁在乎?没有它你就不能让你的代码工作,所以你不能切换到一些性能较低的替代方案。
【解决方案2】:

您可以阅读有关多重继承的信息here

你需要做的是虚拟继承下一个类:

class B : virtual public A {
    int b;
    //...methods..
};

class C : virtual public A {
    int c1,c2;
};

【讨论】:

  • 我已经知道了,正如我所说,我做到了。我只是在问 1)是否有另一种方法可以在不使用多重继承的情况下实现我想要的功能,以及 2)如果我不使用 virtual inh,为什么 b 会被清除。
【解决方案3】:

我不知道您为什么会看到这种行为。如 cmets 中所述,您所描述的代码不应编译。

是否存在设计问题?是的。不要根据查询对象的类型明确做出选择。这就是虚函数的用途。

class A
{
public:
    virtual void prepare_for_list_insertion() {}
};

class B : public A
{
    int b;
};

class C : public A
{
private:
    int c1, c2;

protected:
    void clear() { c1 = c2 = 0; }
};

class D : public B, public C
{
public:
    void prepare_for_list_insertion()
    {
        clear();
    }
};

void func(A* a)
{
    a->prepare_for_list_insertion();
}

int main()
{
    A a;
    func(&a); // calls A::prepare_for_list_insertion
    D d;
    // You need a cast to disambiguate the A base - either will do.
    func(static_cast<C*>(&d)); // calls D::prepare_for_list_insertion
    func(static_cast<B*>(&d)); // calls D::prepare_for_list_insertion
}

【讨论】:

  • hmm.. 确实很有趣.. 只是想知道,static_cast 会以任何方式影响运行时性能吗?
  • Static_cast 在这种情况下与您的 C 样式转换执行相同的操作;它只是更具体。 C 风格的演员表可能意味着不同的东西。这是一篇关于在层次结构中进行转换所涉及的(可能的)指针操作的好文章:drdobbs.com/184402074
  • 可悲的是,那篇文章中的图表链接似乎已损坏......我仍然认为它是可以理解的。
  • 我不同意这一点。虚函数必须在所有派生类中都有一个常量签名,很可能涉及处理一个派生类的类型不应该暴露给另一个派生类,或者除了一个派生类之外没有派生类会实现它们。 dynamic_cast 的用例很少见,但它们确实存在,而且它的使用本身不是设计问题。
  • 我决定摆脱实现虚函数的多重继承:struct extra_ints { int c1,c2; }; A类{虚拟extra_ints * get_ints(){返回NULL; } } B 类:公共 A { int b; } 类 D:公共 B { extra_ints e_i; extra_ints* get_ints() { 返回 &e_i;当我需要额外的整数时(当 is_the_case 时)我可以 d->get_ints()->c1=123;你认为这是一个糟糕的设计还是什么?我对此不太满意.. 在这个例子中我只有 3 个类 D、E、F.. 但如果我有 200 个 Id 必须添加 200 个相同的功能.. 我错过了什么吗?
猜你喜欢
  • 2013-10-15
  • 1970-01-01
  • 2014-06-28
  • 2017-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多