C++ 多重虚拟继承与 COM

发布于 2024-07-09 05:56:59 字数 1353 浏览 7 评论 0原文

网络上充斥着对“可怕的钻石问题”的解释。 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 */ };

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

我真正的问题是试图理解 我们在 CodeProject 的朋友那里进行了讨论。 它涉及一个用于为 Flash 播放器创建透明容器的自定义类。

我想我会尝试这个地方的乐趣。 事实证明,使用版本 10 的 Flash 播放器,以下声明会使您的应用程序崩溃。

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

调试表明,当进入函数实现(QueryInterface 等)时,从不同的调用者处,我会为不同的调用获得不同的“this”指针值。 但是删除“虚拟”就可以了!没有崩溃,并且有相同的“this”指针。

我想清楚地了解究竟发生了什么。 多谢。

干杯 亚当

The net is overflowing with explanations of the "dreaded diamond problem".
So is StackOverflow. I think I understand that bit, but I fail to translate that knowledge into comprehending something similar yet different.

My question begins as a pure C++ question, but the answer might well branch over into MS-COM specifics. The general problem question goes:

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 */ };

This is not the classic diamond solution. Exactly what does "virtual" do here?

My real problem is trying to understand a discussion over at our friends' place at CodeProject. It involves a custom class for creating a transparent container for the Flash player.

I thought I would try this place for fun. It turns out that the following declaration crashes your app, with version 10 of the Flash player.

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

Debugging shows that when entering the function implementations (QueryInterface etc), from different callers, I get different "this"-pointer values for different calls.
But removing "virtual" does the trick! No crashes, and same "this"-pointer.

I would like to clearly understand exactly what is going on. Thanks a lot.

Cheers
Adam

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

三五鸿雁 2024-07-16 05:56:59

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

虚拟继承的类只是向编译器标记它应该合并 Der1Der2 的更高版本。 由于继承树中只有一个出现,所以什么也不做。 虚拟对Base没有影响。

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

虚拟继承仅影响下一个继承的类,并且仅影响已声明为虚拟的实例。 这与您的预期有所不同,但它是对类编译方式的限制。

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 中标记为 virtual 的原因之一是 C 和 B 需要知道不要调用 A 的构造函数。 通常他们会初始化每个副本。 当他们参与钻石继承时,他们就不会这样做。 但是你不能重新编译 B 和 C 来不构造 A。这意味着 C 和 B 必须提前知道创建构造函数代码,其中 A 的构造函数未被调用。

The virtual inheritance in the first example don't do anything. I would wager that they compile to the same code if they were removed.

The virtually inherited class just flag the compiler that it should merge later versions of Der1 or Der2. Since only one of each appears in the inheritance tree nothing is done. The virtuals have no effect on Base.

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

The virtual inheritance only effects the next inherited class, and only for instances that have been delcared virtual. This is backward from what you would expect, but it's a limitation on the way classes are compiled.

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

One of the reasons A must be marked virtual in C and B instead of E or F is that C and B need to know not to call A's constructor. Normally they would have initialize each of their copies. When they are involved in diamond inheritance they wont. But you cant recompile B and C to not construct A. That means C and B have to know ahead of time to create constructor code where A's constructor is not called.

弄潮 2024-07-16 05:56:59

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

COM 要求当您在 IUnknown 对象上调用 IQueryInterface 时,该对象公开的所有接口都返回相同 IUnknown ...此实现显然破坏了这一点。

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

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

I think the issue with your COM example is that by adding the virtual keyword you are saying that all the IOle* interfaces share a common IUnknown implementation. In order to implement this the compiler has to create multiple v-tables, hence you different 'this' values depending on the derived class it came down.

COM requires that when you call IQueryInterface on an object for IUnknown that ALL interfaces exposed by the object return the same IUnknown ... which this implementation clearly breaks.

Without the virtual inheritance each IOle* nominally has its own IUnknown implementation. However, since IUnknown is an abstract class, and doesn't have any storage the compiler, and all the IUnknown implementations come from FlashContainerWnd there is only a single implementation.

(OK, so that last bit sounds weak ... perhaps someone with a better grasp of the language rules can explain it more clearly)

贪了杯 2024-07-16 05:56:59

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

第 96 页左右有对虚拟继承的解释,它专门解决了菱形问题。

我将让您阅读详细信息,但基本上,虚拟继承的使用需要在虚拟表中查找才能找到基类。 在正常继承中情况并非如此,正常继承中可以在编译时计算基类位置。

(上次我采取了简单的方法,只是推荐了一本书来回答堆栈溢出问题,我得到了很大的支持,所以让我们看看这种情况是否会再次发生......:)

It's a bit dated now, but the best reference I have ever come across that concerns C++ internals is Lippman's Inside The C++ Object Model. The exact implementation details may not match your compiler's output, but the understanding it provides is extremely valuable.

Around page 96 there is an explanation of virtual inheritance and it specifically addresses the diamond problem.

I'll leave you to read the details but basically the use of virtual inheritance requires a lookup in the virtual table in order to locate the base class. This is not the case in normal inheritance, where the base class location can be calculated at compile time.

(The last time I took the easy way out and just recommended a book to answer a stack overflow question I got voted up considerably, so let's see if that happens again... :)

绳情 2024-07-16 05:56:59

我想我只是尝试一下你的例子。 我想出了:

#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 指针始终相同,几乎与预期一致。

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

关于“虚拟”的作用,我发现 wikipedia 上的文章很有启发性,尽管如此,似乎重点关注钻石问题

I thought I'd just try your example. I came up with:

#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;
}

It produces the following output:

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)

So, when casting a Join2 to its base classes, you might get different pointers, but the this pointer passed to say_hi() is always the same, pretty much as expected.

So, basically, I cannot reproduce your problem, making it kind of hard to answer your real question.

Regarding wat "virtual" does, I found the article on wikipedia enlightening, though that, too, seems to focus on the diamond problem

巷子口的你 2024-07-16 05:56:59

正如卡斯平所说,你的第一个例子实际上并没有做任何有用的事情。 然而,它要做的是添加一个 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

As Caspin says, your first example doesn't actually do anything useful. What it will do however, is add a vpointer to tell derivative classes where to find the classes it inherited from.

This fixes any diamonds you may now create (which you don't), but since the class structure is now no longer static, you cannot use static_cast on it any more. I'm unfamiliar with the API involved, but what Rob Walker says about IUnkown may be related to this.

In short, normal inheritance should be used when you need your own baseclass, that shouldn't be shared with 'sibling' classes: (a is a container, b,c,d are parts that each have a container, e combines these parts (bad example, why not use composition?))

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

While virtual inheritance is for when your baseclass should be shared with them. (a is vehicle, b,c,d are different specializations of vehicle, e combines these)

   a
 / | \
b  c  d <-- b, c and d inherit a virtually
 \ | /
   d
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文