What's the difference between a derived object and a base object in c++?

c++, compiler-construction

Solution

The derived object inherits all the data and member functions of the base class. Depending on the nature of the inheritance (public, private or protected), this will affect the visibility of these data and member functions to clients (users) of your class.

Say, you inherited B from A privately, like this:

  class A
  {
    public:
      void MyPublicFunction();
  };

  class B : private A
  {
    public:
      void MyOtherPublicFunction();
  };

Even though A has a public function, it won't be visible to users of B, so for example:

  B* pB = new B();
  pB->MyPublicFunction();       // This will not compile
  pB->MyOtherPublicFunction();  // This is OK

Because of the private inheritance, all data and member functions of A, although available to the B class within the B class, will not be available to code that simply uses an instance of a B class.

If you used public inheritance, i.e.:

  class B : public A
  {
    ...
  };

then all of A's data and members will be visible to users of the B class. This access is still restricted by A's original access modifiers, i.e. a private function in A will never be accessible to users of B (or, come to that, code for the B class itself). Also, B may redeclare functions of the same name as those in A, thus 'hiding' these functions from users of the B class.

As for virtual functions, that depends on whether A has virtual functions or not.

For example:

  class A
  {
    public:
      int MyFn() { return 42; }
  };

  class B : public A
  {
    public:
      virtual int MyFn() { return 13; }
  };

If you try to call `MyFn()` on a B object through a pointer of type A*, then the virtual function will not be called.

For example:

A* pB = new B();
pB->MyFn(); // Will return 42, because A::MyFn() is called.

but let's say we change A to this:

  class A
  {
    public:
      virtual void MyFn() { return 42; }
  };

(Notice A now declares `MyFn()` as virtual)

then this results:

A* pB = new B();
pB->MyFn(); // Will return 13, because B::MyFn() is called.

Here, the B version of `MyFn()` is called because the class A has declared `MyFn()` as virtual, so the compiler knows that it must look up the function pointer in the object when calling `MyFn()` on an A object. Or an object it thinks it is an A, as in this case, even though we've created a B object.

So to your final question, where are the virtual functions stored?

This is compiler/system dependent, but the most common method used is that for an instance of a class that has any virtual functions (whether declared directly, or inherited from a base class), the first piece of data in such an object is a 'special' pointer. This special pointer points to a 'virtual function pointer table', or commonly shortened to 'vtable'.

The compiler creates vtables for every class it compiles that has virtual functions. So for our last example, the compiler will generate two vtables - one for class A and one for class B. There are single instances of these tables - the constructor for an object will set up the vtable-pointer in each newly created object to point to the correct vtable block.

Remember that the first piece of data in an object with virtual functions is this pointer to the vtable, so the compiler always knows how to find the vtable, given an object that needs to call a virtual function. All the compiler has to do is look at the first memory slot in any given object, and it has a pointer to the correct vtable for that object's class.

Our case is very simple - each vtable is one entry long, so they look like this:

vtable for A class:

+---------+--------------+
| 0: MyFn | -> A::MyFn() |
+---------+--------------+

vtable for B class:

+---------+--------------+
| 0: MyFn | -> B::MyFn() |
+---------+--------------+

Notice that for the vtable for the `B` class, the entry for `MyFn` has been overwritten with a pointer to `B::MyFn()` - this ensures that when we call the virtual function `MyFn()` even on an object pointer of type `A*`, the `B` version of `MyFn()` is correctly called, instead of the `A::MyFn()`.

The '0' number is indicating the entry position in the table. In this simple case, we only have one entry in each vtable, so each entry is at index 0.

So, to call `MyFn()` on an object (either of type `A` or `B`), the compiler will generate some code like this:

pB->__vtable[0]();

(NB. this won't compile; it's just an explanation of the code the compiler will generate.)

To make it more obvious, let's say `A` declares another function, `MyAFn()`, which is virtual, which B does not over-ride/re-implement.

So the code would be:

  class A
  {
    public:
      virtual void MyAFn() { return 17; }
      virtual void MyFn()  { return 42; }
  };

  class B : public A
  {
    public:
      virtual void MyFn() { return 13; }
  };

then B will have the functions `MyAFn()` and `MyFn()` in its interface, and the vtables will now look like this:

vtable for A class:

+----------+---------------+
| 0: MyAFn | -> A::MyAFn() |
+----------+---------------+
| 1: MyFn  | -> A::MyFn()  |
+----------+---------------+

vtable for B class:

+----------+---------------+
| 0: MyAFn | -> A::MyAFn() |
+----------+---------------+
| 1: MyFn  | -> B::MyFn()  |
+----------+---------------+

So in this case, to call `MyFn()`, the compiler will generate code like this:

pB->__vtable[1]();

Because `MyFn()` is second in the table (and so at index 1).

Obviously, calling `MyAFn()` will cause code like this:

pB->__vtable[0]();

because `MyAFn()` is at index 0.

It should be emphasised that this is compiler-dependent, and iirc, the compiler is under no obligation to order the functions in the vtable in the order they are declared - it's just up to the compiler to make it all work under the hood.

In practice, this scheme is widely used, and function ordering in vtables is fairly deterministic, so ABI between code generated by different C++ compilers is maintained, and allows COM interoperation and similar mechanisms to work across boundaries of code generated by different compilers. This is in no way guaranteed.

Luckily, you'll never have to worry much about vtables, but it's definitely useful to get your mental model of what is going on to make sense and not store up any surprises for you in the future.

Problem

What's the difference between a derived object and a base object in c++, especially, when there is a virtual function in the class. Does the derived object maintain additional tables to hold the pointers to functions?

Original source