PolGraphic
PolGraphic

Reputation: 3364

Memory usage by virtual inheritance

I have some classes (most of them are abstract, with virtual inheritance):

class A{
    public:
        virtual void f1() = 0;
        virtual void f2() = 0;
};

class B : virtual public A{
    public:
        virtual void f3() = 0;
};

class MyA : virtual public A{
    public:
        virtual void f1(){ ... }
        virtual void f2(){ ... }
};

class MyB : virtual public B, virtual public MyA{
    public:
        void f3(){ ... }
};

Now I use them:

B * object = new MyB();
object->f1(); //declared in A, imp. in MyA
object->f3(); //declared in B, imp. in MyB

Everything works fine, and the code is "good" for me (I can fast switch from MyB to YourB with changes in 1 line only).

But my question is:

How much additional memory it uses, compared to the similar code listed below (same result, different structure)?

I'm not good with memory layouts/vTables, so please explain it to me in a simple way - I want to know, if my application will spend more resources (memory) and if the executable will be slower?

And I compare that code to that one:

class MyA{
    public:
        virtual void f1(){ ... }
        virtual void f2(){ ... }
};

class MyB : public MyA{
    public:
        void f3(){ ... }
};

MyB * object = new MyB();
object->f1(); //declared in MyA, imp. in MyA
object->f3(); //declared in MyB, imp. in MyB

The sizeof(object) returns 4 in both examples (Win x32, Visual Studio native compiler), but I'm not sure if it's authoritative here. Maybe it doesn't count something - I don't think that both samples are 100% equal.

Upvotes: 0

Views: 1291

Answers (3)

Mike Seymour
Mike Seymour

Reputation: 254431

It depends on the implementation, but typically virtual inheritance will need:

  • an extra pointer (or offset) for each virtual base class: the adjustment to convert (for example) B* to A* will depend on which other subobjects in the same object also derive virtually from A. I think this can be stored in the vtable, rather than the object itself, so that the overhead could be per-class rather than per-object.
  • extra logic in the constructor and destructor, to determine whether or not the virtual base object needs initialising/destroying at that point.
  • extra work for some pointer conversions, reading the stored pointer/offset rather than applying a compile-time constant.

For memory usage, you can measure the per-object overhead in your implementation by printing sizeof (MyB). Any per-class overhead will probably be negligible, unless you have a huge number of classes.

Upvotes: 2

Rakib
Rakib

Reputation: 7625

For commonly used compilers, the overhead will be roughly as following

  1. Memory Overhead: extra pointer to vtable for each parent class.
  2. Runtime Overhead: each virtual method call will have indirections to find the right implementation of the method for actual type of the invoking object.

sizeof(object) returns 4 in both case because you are measuring size of pointer to object, not object itself. Pointer size is same no matter to which object it points.

Upvotes: 1

Thomas Matthews
Thomas Matthews

Reputation: 57678

Before I start, your concern is known as a premature optimization. There are other concerns to worry about before size and space: correctness and robustness.

Inheritance is usually implement through Virtual Function Tables, in essence, a table of addresses to functions. So the amount of extra code space depends on the quantity of virtual functions.

Virtual functions are executed using a jump table. A common practice is to load the Program Counter with the value in the Function Table. This is usually 2 assembly instructions.

The amount of variable space for the function table may be less than the memory wasted by alignment padding in a structure. The wasted execution time is less than the overhead to call a function.

Edit 1:
BTW, assembly instructions are usually executed in 1 microsecond or less. So calling through a function table would take 2 microseconds. Compare this with waiting for disk I/O or User I/O.

This is why it is a premature optimization: optimizing before profiling. Profile the entire code and attend to the bottlenecks before worrying about this waste of code and variable space.

Upvotes: 2

Related Questions