【问题标题】:Multiple inheritance of virtual classes虚拟类的多重继承
【发布时间】:2011-10-15 06:03:49
【问题描述】:

假设我有以下代码:

class a {
public:
    virtual void do_a() = 0;
}

class b {
public:
    virtual void do_b() = 0;
}

class c: public a, public b {
public:
    virtual void do_a() {};
    virtual void do_b() {};
}

a *foo = new c();
b *bar = new c();

foo->do_a()bar->do_b() 会起作用吗?这里的内存布局是什么?

【问题讨论】:

  • 你的意思是foo->do_a()bar->do_b()吗?

标签: c++ oop virtual multiple-inheritance


【解决方案1】:
foo->do_a();   // will work
bar->do_b();   // will work
bar->do_a();   // compile error (do_a() is not a member of B)
foo->do_b();   // compile error (do_b() is not a member of A)

// If you really know the types are correct:
C* c = static_cast<C*>(foo);

c->do_a();  // will work
c->do_b();  // will work

// If you don't know the types, you can try at runtime:
if(C* c = dynamic_cast<C*>(foo))
{
    c->do_a();  // will work
    c->do_b();  // will work
}

【讨论】:

  • 我认为这是被问到的,这就是为什么我将它们包含在答案中。它们不起作用(编译错误)。更详细地更新了答案。
  • @Chad 我在谈论变量的名称。 a->do_a() 不起作用, foo->do_a() 会。 :)
  • 是的,后来注意到了——再次更新。至少我对此投了反对票;)
【解决方案2】:

a->do_a() 和 b->do_b() 会起作用吗?

假设您的意思是foo-&gt;do_a()bar-&gt;do_b(),因为ab 不是object,它们是type,是的。他们会工作的。你试过运行它吗?

这里的内存布局是什么?

这主要是实现定义的。幸运的是,除非您想编写不可移植的代码,否则您不需要知道这一点。

【讨论】:

    【解决方案3】:

    他们为什么不应该呢?内存布局通常是这样的:

    +----------+
    |  A part  |
    +----------+
    |  B part  |
    +----------+
    |  C part  |
    +----------+
    

    如果您将 foobar 转换为 void* 并显示它们,您将 获得不同的地址,但编译器知道这一点,并会安排 在调用 this 指针时正确修复 功能。

    【讨论】:

      【解决方案4】:

      a->do_a() 和 b->do_b() 会起作用吗?

      没有。

      foo-&gt;do_a()bar-&gt;do_b() 会起作用吗?

      是的。您的代码是虚函数调度的典型示例。

      你为什么不试试?

      这里的内存布局是什么?

      谁在乎?

      (即这是实现定义的,从您那里抽象出来。您不需要也不想知道。)

      【讨论】:

        【解决方案5】:

        他们会工作的。就内存而言,这取决于实现。您已经在堆上创建了对象,对于大多数系统,值得注意的是堆上的对象向上增长(参见堆栈向下增长)。所以可能,你会有:

        Memory:
        
        +foo+
        -----
        +bar+
        

        【讨论】:

          【解决方案6】:

          是的,当然可以。不过,机制有点棘手。该对象将有两个 vtable,一个用于 class a 父级,一个用于 class b 父级。指针将被调整,使它们指向与指针类型对应的对象子集,导致了这个令人惊讶的结果:

          c * baz = new c;
          a * foo = baz;
          b * bar = baz;
          assert((void *)foo == (void *)bar); // assertion fails!
          

          编译器知道赋值时的类型,并且确切地知道如何调整指针。

          这当然完全依赖于编译器; C++ 标准中没有任何内容表明它必须以这种方式工作。只是它必须工作。

          【讨论】:

            【解决方案7】:

            正如其他人所提到的,以下内容将毫无问题地工作

            foo->do_a();
            bar->do_b();
            

            但是,这些不会编译

            bar->do_a();
            foo->do_b();
            

            由于barb* 类型,它不知道do_afoodo_b 也是如此。如果你想进行这些函数调用,你必须向下转型。

            static_cast<c *>(foo)->do_b();
            static_cast<c *>(bar)->do_a();
            

            您的示例代码中未显示的另一个非常重要的事情是,当通过基类指针继承和引用派生类时,基类必须有一个虚拟析构函数。如果不是,那么以下将产生未定义的行为。

            a* foo = new c();
            delete a;
            

            修复很简单

            class a {
            public:
                virtual void do_a() = 0;
            
                virtual ~a() {}
            };
            

            当然,b 也需要进行此更改。

            【讨论】:

              猜你喜欢
              • 2013-11-19
              • 2017-03-31
              • 2015-11-21
              • 1970-01-01
              • 1970-01-01
              • 2014-10-12
              • 2012-10-11
              • 1970-01-01
              • 2014-11-06
              相关资源
              最近更新 更多