0

Where can I find a good explanation of C++ stateful virtual base?

I looked in the JSF C++ Coding Standard and read their explanation, but was looking for some additional information.

Thank you for any additional details provided.

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
JustADude
  • 2,619
  • 7
  • 31
  • 45
  • I've never heard the term, but I would _speculate_ that is a base class with virtual member functions that _also_ has member variables. Such a thing isn't intrinsically dangerous, but on the other hand, it's easy to screw up the invariants. – Mooing Duck May 30 '12 at 15:53
  • Are you talking about [virtual inheritance](http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fcplr135.htm)? If so, see also [this question](http://stackoverflow.com/questions/21558/in-c-what-is-a-virtual-base-class). – Luc Touraille May 30 '12 at 15:57
  • 2
    You can think about it this way: If a *member function* is virtual, it means that the actual function has to be looked up at runtime, depending on the actual most-derived class. If a *base class* is virtual, it means that the *actual* base class has to be looked up at runtime. This is solve like Every Programming Problem: with an extra layer of indirection. – Kerrek SB May 30 '12 at 16:00
  • @KerrekSB, brilliant short explanation, you should add it as an answer. – Nikolai Fetissov May 30 '12 at 16:51
  • @NikolaiNFetissov: OK, I've elaborated a bit on that idea. – Kerrek SB May 30 '12 at 17:20

2 Answers2

3

Stanley Lippman's book "Inside the C++ Object Model" has a great run-through of the subject (though dated, but still valid).

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • nice, detailed explanation, but you failed to delineate what part of this is actually the "stateful virtual base." – skittlebiz Aug 19 '21 at 17:06
2

Virtuality is about indirection. Let's start simple:

struct Foo { void bar(int, bool) {} } x;
x.bar(12, false);

Here the call to Foo::bar() for the instance x is fully known at compile time and resolved statically: One fixed function which is given the instance reference and the function arguments. Function name, call, done. No problem so far.

Moving on:

struct Boo { virtual void bar(char, float) = 0; };
extern Boo * foreign_function();

Boo * p = foreign_function();
p->bar('a', -1.5);

This time, there is no way to know at compile time where the bar() call is supposed to go. The only way to resolve this is to add a level of indirection that allows you to look up all possible overrides of this member function and pick the correct one at runtime, depending on the dynamic type of *p. This time we start with the function name, perform the lookup at runtime, and then make the call. This pattern should still be fairly familiar.

The point here is that it is enough to know that the dynamic type of *p is a subtype of the (non-virtual) base Boo so that we can implement this with just one single lookup (e.g. a vtable pointer to a table that's compatible with that of Boo).

Now on to the big fish:

struct Voo { virtual void doo(double, void *) = 0; };
struct Left  : virtual Voo { virtual void doo(double, void *); } };
struct Right : virtual Voo { virtual void doo(double, void *); } };
struct Most : Left, Right  { virtual void doo(double, void *); } };

Left * p = /* address of a Most object, say */;
p->doo(0.1, nullptr);

We already know that we don't know where doo() is supposed to go, and we have to look it up at runtime. However, a simple one-step indirection is no longer possible. Even though Left is a subclass of Voo and Right also is a subclass of Voo, the actual Voo base subobject of *p is not actually a subobject of the Left- or of the Right-subobject -- the (unique!) virtual subobject belongs to Most directly (or whatever the most-derived object is). In implementation terms, a single vtable pointer is no good, because we don't want Left's vpointer, nor do we want Right's vptr. Instead, we want whichever vpointer the actual object has.

So now we find ourselves in a familiar situation: We need to look something up that we can only know at runtime. And this time the thing we need to look up is the actual virtual base. So the process goes like this: Function name, look up virtual base at runtime, look up virtual function in virtual base, and make the call. (In the typical vtable implementation of virtuality, this is usually done with an extra piece of lookup via a "thunk", or a "pointer to a pointer".)

In a nutshell, "virtual" means "determined at runtime".

(That does not force your compiler to generate runtime code. If the target of the dispatch is provably known at compile time, the call may be devirtualized. But the behaviour of your program is "as if".)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084