C ++多虚拟继承与COM

时间:2008-11-18 17:58:18

标签: c++ com multiple-inheritance virtual-inheritance

网上满是"dreaded diamond problem"的解释。 StackOverflow也是如此。我想我明白了这一点,但我没有将这些知识转化为理解类似但不同的东西。

我的问题始于一个纯粹的C ++问题,但答案很可能转移到MS-COM细节中。一般问题是:

class Base { /* pure virtual stuff */ };
class Der1 : Base /* Non-virtual! */ { /* pure virtual stuff */ };
class Der2 : Base /* Non-virtual! */ { /* pure virtual stuff */ };
class Join : virtual Der1, virtual Der2 { /* implementation stuff */ };
class Join2 : Join { /* more implementation stuff + overides */ };

这是不是经典钻石解决方案。究竟“虚拟”在这里做什么?

我真正的问题是尝试理解discussion over at our friends' place at CodeProject.它涉及一个自定义类,用于为Flash播放器创建透明容器。

我以为我会尝试这个地方玩得开心。事实证明,以下声明会使您的应用程序崩溃,其中包含Flash播放器的第10版。

class FlashContainerWnd:   virtual public IOleClientSite,
                           virtual public IOleInPlaceSiteWindowless,
                           virtual public IOleInPlaceFrame,
                           virtual public IStorage

调试显示,当从不同的调用者进入函数实现(QueryInterface等)时,我得到不同调用的不同“this” - 指针值。 但删除“虚拟”可以解决问题!没有崩溃,同样是“这个” - 指针。

我想清楚地了解到底发生了什么。非常感谢。

干杯 亚当

5 个答案:

答案 0 :(得分:3)

第一个示例中的虚拟继承不执行任何操作。如果删除它们,我会打算编译成相同的代码。

虚拟继承的类只标记编译器应该合并更高版本的Der1Der2。由于每个中只有一个出现在继承树中,所以没有做任何事情。虚拟对Base没有影响。

auto p = new Join2;
static_cast<Base*>(static_cast<Der1*>(p)) !=
      static_cast<Base*>(static_cast<Der2*>(p))

虚拟继承仅影响下一个继承的类,并且仅适用于已被delcared虚拟的实例。这与你期望的相反,但它是对编译类的方式的限制。

class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public A {};
class E : virtual public A, public B, public C, public D {};
class F : public A, public B, public C, public D {};

F::A != F::B::A or F::C::A or F::D::A
F::B::A == F::C::A
F::D::A != F::B::A or F::C::A or F::A

E::B::A == E::C::A == E::A
E::D::A != E::B::A or E::C::A or E::D::A

A必须在C和B而不是E或F中标记为虚拟的原因之一是C和B需要知道不要调用A的构造函数。通常他们会初始化每个副本。当他们参与钻石继承时,他们不会。但是你不能重新编译B和C而不构造A.这意味着C和B必须提前知道创建构造函数代码,其中没有调用A的构造函数。

答案 1 :(得分:2)

认为您的COM示例的问题是,通过添加virtual关键字,您说所有IOle *接口共享一个共同的IUnknown实现。为了实现这一点,编译器必须创建多个v表,因此根据它所导出的派生类,您可以使用不同的“this”值。

COM要求当你在IUnknown对象上调用IQueryInterface时,对象公开的 ALL 接口会返回相同的 IUnknown ...这个实现明显地破坏了。< / p>

如果没有虚拟继承,每个IOle *名义上都有自己的IUnknown实现。但是,由于IUnknown是一个抽象类,并且编译器没有任何存储空间,并且所有IUnknown实现都来自FlashContainerWnd,因此只有一个实现。

(好吧,所以最后一点听起来很弱......也许有更好地掌握语言规则的人可以更清楚地解释它)

答案 2 :(得分:0)

现在有点过时了,但我遇到过的关于C ++内部的最佳参考是Lippman的Inside the C ++ Object Model。确切的实现细节可能与编译器的输出不匹配,但它提供的理解非常有价值。

第96页左右有虚拟继承的解释,它专门解决钻石问题。

我将让您阅读详细信息,但基本上使用虚拟继承需要在虚拟表中进行查找才能找到基类。在普通继承中不是这种情况,其中基类位置可以在编译时计算。

(上一次我采取了简单的方法,只是推荐了一本书来回答堆栈溢出问题,我得到了相当多的投票,所以让我们看看是否会再次发生......:)

答案 3 :(得分:0)

我以为我只是试试你的榜样。我提出了:

#include "stdafx.h"
#include <stdio.h>

class Base
{
public:
  virtual void say_hi(const char* s)=0;
};

class Der1 : public Base
{
public:
  virtual void d1()=0;
};

class Der2 : public Base
{
public:
  virtual void d2()=0;
};

class Join : virtual public Der1, virtual public Der2
             // class Join : public Der1, public Der2
{
public:
  virtual void say_hi(const char* s);
  virtual void d1();
  virtual void d2();
};

class Join2 : public Join
{
  virtual void d1();
};

void Join::say_hi(const char* s)
{
  printf("Hi %s (%p)\n", s, this);
}

void Join::d1()
{}

void Join::d2()
{}

void Join2::d1()
{
}

int _tmain(int argc, _TCHAR* argv[])
{
  Join2* j2 = new Join2();
  Join* j = dynamic_cast<Join*>(j2);
  Der1* d1 = dynamic_cast<Der1*>(j2);
  Der2* d2 = dynamic_cast<Der2*>(j2);
  Base* b1 = dynamic_cast<Base*>(d1);
  Base* b2 = dynamic_cast<Base*>(d2);

  printf("j2: %p\n", j2);
  printf("j:  %p\n", j);
  printf("d1: %p\n", d1);
  printf("d2: %p\n", d2);
  printf("b1: %p\n", b1);
  printf("b2: %p\n", b2);

  j2->say_hi("j2");
  j->say_hi(" j");
  d1->say_hi("d1");
  d2->say_hi("d2");
  b1->say_hi("b1");
  b2->say_hi("b2");

  return 0;
}

它产生以下输出:

j2: 00376C10
j:  00376C10
d1: 00376C14
d2: 00376C18
b1: 00376C14
b2: 00376C18
Hi j2 (00376C10)
Hi  j (00376C10)
Hi d1 (00376C10)
Hi d2 (00376C10)
Hi b1 (00376C10)
Hi b2 (00376C10)

因此,当将Join2转换为其基类时,您可能会得到不同的指针,但传递给say_hi()的this指针总是相同的,几乎与预期的一样。

所以,基本上,我无法重现你的问题,因此很难回答你的真实问题。

关于wat“虚拟”的确如此,我发现wikipedia上的文章很有启发性,尽管那也似乎关注钻石问题

答案 4 :(得分:0)

正如Caspin所说,你的第一个例子实际上并没有做任何有用的事情。然而,它会做什么,添加一个vpointer来告诉派生类在哪里找到它继承的类。

这修复了你现在可能创建的任何钻石(你没有),但由于类结构现在不再是静态的,你不能再使用static_cast了。我对所涉及的API不熟悉,但Rob Walker关于IUnkown的说法可能与此有关。

简而言之,当您需要自己的基类时,应该使用正常继承,不应该与'兄弟'类共享:( a是容器,b,c,d是部分每个都有一个容器,e组合这些部分(坏的例子,为什么不使用组合?))

a  a  a
|  |  |
b  c  d <-- b, c and d inherit a normally
 \ | /
   e

虽然虚拟继承适用于与它们共享基类的时间。 (a是车辆,b,c,d是车辆的不同专业,e结合这些)

   a
 / | \
b  c  d <-- b, c and d inherit a virtually
 \ | /
   d