views:

1058

answers:

6

Hi!

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

+2  A: 

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)

Rob Walker
yep, I think you're right. You get the IUnknown and then use QueryInterface to get the interface you want. You do not cast a COM object in the usual C++ way.
gbjbaanb
Thanks, if I understand you answer correctly, I should use "virtual", but the fact is is that I have to remove it, to stop the crash. So I fail to make any sense of how this ansewrs my question.
Adam
+2  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.

caspin
I am not sure what you mean by "instances that have been delcared virtual". If you mean subsequnet virtual inheritance, then my real world problem, unfortunately, proves your answer wrong. Because I only have virtual in a single level. With virtual: crash. Without virtual: good.
Adam
A: 

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... :)

Alastair
Thanks, but this is *not* the diamond problem, if you read my question closely.
Adam
A: 

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

Kees-Jan
A: 

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
AI0867
A: 

Does this answer your question? http://stackoverflow.com/questions/3081871/why-cant-we-use-virutual-inheritance-in-com

rwong
I think you're right. The other article answers this question 19 months after this question was posted.
Windows programmer