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