4

I was working the last 5 years with the assumption that virtual inheritance breaks static composition.

But now I discovered, that static composition is still maintained, there is just additional information about the location of the correct instance. Is this right?

Daniel Heilper
  • 1,182
  • 2
  • 17
  • 34
Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151
  • 1
    how do you define "static composition" and "breaks"? – lijie Dec 02 '10 at 13:06
  • @lijie static = during compilation, composition = well, composition, breaks = doesn't use / walks around / doesn't involve – Šimon Tóth Dec 02 '10 at 13:07
  • @lijie For example PIMPL breaks static composition. – Šimon Tóth Dec 02 '10 at 13:10
  • @Let: Unless I'm misunderstanding you, no it doesn't. The memory layout of the object that has a pointer to the pimpl object never changes. Is that what you mean? – John Dibling Dec 02 '10 at 13:17
  • @John Man I just suck when it comes to phrasing precise sentences in English. I guess I should say "breaks the static binding with the original class" which it does, since static composition with the original class is no longer used (and the size of the class is therefore irrelevant). – Šimon Tóth Dec 02 '10 at 13:20
  • 2
    @Let: I'm just not understanding you. Can you illustrate in your OP with (psudo)code or maybe even some fancy ascii art? – John Dibling Dec 02 '10 at 13:21
  • @Let: From that statement (about pimpl), I infer that static composition means that the memory area for the subobject is contained within the memory area of the enclosing object? – lijie Dec 02 '10 at 13:22
  • @lijie: Thats kind of what I though too. See my post below with code – John Dibling Dec 02 '10 at 13:35
  • These diagrams might help: http://stackoverflow.com/questions/1129894/why-cant-you-use-offsetof-on-non-pod-strucutures-in-c/1130760#1130760 – Steve Jessop Dec 02 '10 at 13:36
  • @lijie Yeah I guess. Breaks static composition = the base class is no longer static (compile time assigned) part of the parent. – Šimon Tóth Dec 02 '10 at 13:37
  • @Let: What does base and parent mean with respect to PIMPL? Edit: Sorry, I read the explanation under my answer. I think I understand what you meant. – haggai_e Dec 02 '10 at 14:04
  • @Let: Hmm. suppose D virtually inherits B, C which inherit A. Then, the A sub-object is contained inside, D, but may not be contained inside the B sub-object of D. Well. it is just different from the normal convention that we think of C's (the language) structures. – lijie Dec 02 '10 at 17:32

3 Answers3

21

Data Layout in non-virtual Inheritance:

class Point2d {
    int x_, y_;
};

class Point3d : public Point2d {
    int z_;
};

Point2d:

+--------------+
| int x_       |
+--------------+
| int y_       |
+--------------+

Point3d:

+--------------+   --+
| int x_       |     |
+--------------+     +-- Point2d subobject
| int y_       |     |
+--------------+   --+
| int z_       |
+--------------+

Point3d is statically composed of Point2d and the member of Point3d.

Under virtual inheritance

Implemented with an offset variable inside the object.

class Point3d : public virtual Point2d {
    int z_;
};

Point3d:

+-----------------+
| int z_          |
+-----------------+
| Point2d* _vbase |   --> offset to Point2d subobject (2 in this case)
+-----------------+   --+
| int x_          |     |
+-----------------+     +-- Point2d subobject
| int y_          |     |
+-----------------+   --+

Accessing Point3d* point3d->x_ in this context will be translated to (C++ Pseudocode):

(static_cast<Point2d*>(point3d) + point3d->_vbase)->x_

Note that there are different ways to implement virtual inheritance like offset pointers inside the vtable, this is just one way to implement virtual inheritance. I chose this one because indirection via vtables would require more ascii drawing.

Virtual inheritance has no benefit here and I would expect (as @Matthieu noted in the comments) a compiler to optimize this class so that it's internal data layout is the same as in non-virtual inheritance. Virtual inheritance is only beneficial in multiple inheritance (see Vertex3d class below).

How does this look like in multiple inheritance?

 class Vertex : virtual Point2d {
     Vertex* next_;
 };

 class Vertex3d : public Point3d, public Vertex {
 };

Vertex:

+-----------------+
| Vertex* next_   |
+-----------------+
| Point2d* _vbase |   --> offset of Point2d subobject (2 in this case)
+-----------------+   --+
| int x_          |     |
+-----------------+     +-- Point2d subobject
| int y_          |     |
+-----------------+   --+

Vertex3d:

+------------------+   --+
| int z_           |     |
+------------------+     +-- Point3d subobject
| Point2d* _vbase1 |     |--> offset to Point2d subobject (4 in this case)
+------------------+   --+
| Vertex* next_    |     |
+------------------+     +-- Vertex subobject 
| Point2d* _vbase2 |     |--> offset to Point2d subobject (2 in this case)
+------------------+   --+
| int x_           |     |
+------------------+     +-- shared Point2d subobject
| int y_           |     |   both Point3d and Vertex point to this 
+------------------+   --+   single copy of Point2d

In virtual multiple inheritance both base classes Vertex and Point3d share the base Point2d in Vertex3d. non-virtual inherited members are layed out as usual.

The point of virtual multiple inheritance is that all descendants of Point3d and Vertex will share one copy of Point2d. Without virtual multiple inheritance (= "ordinary" multiple inheritance) both the Point3d subobject and the Vertex subobject of Vertex3d would have its own copy of Point2d:

Layout of Vertex3d without virtual multiple inheritance:

+------------------+   --+
| int z_           |     |
+------------------+     +-- Point3d subobject --+
| int x_           |     |                       |
+------------------+     |                       +-- Point2d subobject
| int y_           |     |                       |   of Point3d
+------------------+   --+                     --+
| Vertex* next_    |     |
+------------------+     +-- Vertex subobject  --+
| int x_           |     |                       |
+------------------+     |                       +-- Point2d subobject
| int y_           |     |                       |   of Vertex
+------------------+   --+                     --+

References:

  • Lippman: Inside the C++ Object Model. Chapter 3
WolfgangP
  • 3,195
  • 27
  • 37
  • OK, the only thing still eluding me is whether the one shared instance of Point2d is still part of one of the direct descendants or just the final class. – Šimon Tóth Dec 02 '10 at 13:48
  • @Let_Me_Be: I don't understand your question. What does "being part" mean to you ? You can create a free-standing "Point3d" or a bigger "Vertex3d" and in both cases a "Point2d" subobject will exist (and be unique). – Matthieu M. Dec 02 '10 at 13:56
  • @M `struct X : A, C, D {};` where C and D virtually inherit B. Will B be only part of X (can it be actually before A?), or will B be already part of C, or is it irrelevant since it will be after A and therefore it doesn't matter if we consider B part of X or already C (which is part of X anyway). – Šimon Tóth Dec 02 '10 at 14:00
  • 1
    B will be part of C, as well as of D; Look at my examples: Point2d is (of course) part of Vertex and part of Point3d. B will also be part of X but since you inherit B virtually there will be just one copy of B inside of X and the C and D subobjects of X point to that single copy. – WolfgangP Dec 02 '10 at 14:03
  • @Wolfgang I meant static part, yes it will behave as if it is part of both C and D (and X through C and D), but that's not what I'm asking. – Šimon Tóth Dec 02 '10 at 14:07
  • 1
    @Let_Me_Be: it will be part of X, that is sure, whether it's also part of C or not is irrelevant here as far as I am concerned. In fact, in case of alignment issues, one could expect a smart compiler to reorder the subobjects in memory to gain some space. What Wolfgang draw is an example implementation, not a definite one. I am not even sure that the single block allocation is required (by the standard), though it would seem wasteful to do otherwise. – Matthieu M. Dec 02 '10 at 14:09
  • @M You're very right. I would also expect a compiler to optimize `Point3d` and `Vertex` so that they're not distinguishable from non-virtual inherited classes. The added level of indirection is not needed here. It just comes in play at `Vertex3d`. – WolfgangP Dec 02 '10 at 14:19
  • Fantastic effort on the diagrams; I wish I weren't out of upvotes for the day. – Karl Knechtel Dec 02 '10 at 14:20
3

Objects of classes that use virtual inheritance have a fixed memory layout that is determined in compilation time. Accessing the virtual base however requires a level of indirection since you cannot tell where it is relative to the derived pointer.

See Wikipedia

haggai_e
  • 4,689
  • 1
  • 24
  • 37
  • 1
    +1, if you mean that memory layout is fixed for complete objects of class X. If X is a base class, then the corresponding subobject is of class X, but it will sometimes have a different memory layout from that fixed layout, in that its own virtual bases will be in a different place according to what this subobject is a base class of. – Steve Jessop Dec 02 '10 at 13:29
  • @Steve: But @Let also said that pimpl breaks it, and I'm having a hard time reconciling that – John Dibling Dec 02 '10 at 13:36
  • 1
    @John: I *think* he just means that in pimpl the "object's data" is off at a location that's completely undetermined at compile-time, and that until half an hour ago, he thought virtual bases were separately allocated in the same way. Of course in pimpl it's not really "the object's data" as far as the language is concerned. So I don't really understand the term "static composition" either, but I'm basing what I say on the questioner's comments here: http://stackoverflow.com/questions/4335071/why-size-of-extreme-down-derived-class-multiple-virual-inhertance-includes-two/4335128#4335128 – Steve Jessop Dec 02 '10 at 13:41
  • @Steve I guess I should have skipped the static composition talk. Since that is all that C++ has and everything else is not composition from the C++ viewpoint. – Šimon Tóth Dec 02 '10 at 13:43
  • 1
    @Let_Me_Be: yes, if it's some kind of functional programming and/or prototype-style-inheritance talk, then perhaps it's just not really a meaningful concept in C++. If I'm understanding the term correctly, though, then you might say that complete objects and member subobjects in C++ always have it, but base class subobjects don't necessarily. – Steve Jessop Dec 02 '10 at 13:50
  • @Steve Oh well, I just always learn language concepts through more generic theoretical constructions. It helps a lot when new languages come along. – Šimon Tóth Dec 02 '10 at 13:52
0

Maybe I'm dumb, but I don't understand what you mean by "static composition." You say pimpl breaks it, so let's start with that and take polymorphism and virtual inheritance out of it.

Suppose you have this code:

#include <iostream>
using namespace std;

class Implementation
{
public:
    bool do_foo() { return true; }
};

class Implementation2
{
public:
    bool do_foo() { return false; }
private:
    char buffer_[1024];
};

class Interface
{
public:
    Interface(void* impl) : impl_(impl) {};
    bool foo() { return reinterpret_cast<Implementation*>(impl_)->do_foo(); }
    void change_impl(void* new_impl) { impl_ = new_impl; }

private:
    void* impl_;
};

int main()
{
    Implementation impl1;
    Implementation2 impl2;

    Interface ifc(&impl1);
    cout << "sizeof(ifc+impl1) =  " << sizeof(ifc) << "\n";

    Interface ifc2(&impl2);
    cout << "sizeof(ifc2+impl2) =  " << sizeof(ifc2) << "\n";

    ifc.change_impl(&impl2);
    cout << "sizeof(ifc+impl2) =  " << sizeof(ifc) << "\n";

    cout << "sizeof(impl) = " << sizeof(impl1) << "\n";
    cout << "sizeof(impl2) = " << sizeof(impl2) << "\n";

}

When you say "breaks static composition" do you mean the sizeof things change as you change out the pimpl in the interface?

John Dibling
  • 99,718
  • 31
  • 186
  • 324