Tuesday, January 18, 2011

Virtual base classes

Note: This section is an advanced topic and can be skipped or skimmed if desired.
In the previous section on multiple inheritance, we left off talking about the “diamond problem”. In this section, we will resume this discussion.
Virtual base classes
Here is our example from the previous lesson, with some constructors:
01class PoweredDevice
02{
03public:
04    PoweredDevice(int nPower)
05    {
06        cout << "PoweredDevice: " << nPower << endl;
07    }
08};
09 
10class Scanner: public PoweredDevice
11{
12public:
13    Scanner(int nScanner, int nPower)
14        : PoweredDevice(nPower)
15    {
16        cout << "Scanner: " << nScanner << endl;
17    }
18};
19 
20class Printer: public PoweredDevice
21{
22public:
23    Printer(int nPrinter, int nPower)
24        : PoweredDevice(nPower)
25    {
26        cout << "Printer: " << nPrinter << endl;
27    }
28};
29 
30class Copier: public Scanner, public Printer
31{
32public:
33    Copier(int nScanner, int nPrinter, int nPower)
34        : Scanner(nScanner, nPower), Printer(nPrinter, nPower)
35    {
36    }
37};
If you were to create a Copier class object, by default you would end up with two copies of the PoweredDevice class — one from Printer, and one from Scanner. This has the following structure:

We can create a short example that will show this in action:
1int main()
2{
3    Copier cCopier(1, 2, 3);
4}
This produces the result:
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
As you can see, PoweredDevice got constructed twice.
While this is sometimes what you want, other times you may want only one copy of PoweredDevice to be shared by both Scanner and Printer. To share a base class, simply insert the “virtual” keyword in the inheritance list of the derived class. This creates what is called a virtual base class, which means there is only one base object that is shared. Here is the an example (without constructors for simplicity) showing how to use to virtual keyword to create a shared base class:
01class PoweredDevice
02{
03};
04 
05class Scanner: virtual public PoweredDevice
06{
07};
08 
09class Printer: virtual public PoweredDevice
10{
11};
12 
13class Copier: public Scanner, public Printer
14{
15};
Now, when you create a Copier class, you will get only one copy of PoweredDevice that will be shared by both Scanner and Printer.
However, this leads to one more problem: if Scanner and Printer share a PoweredDevice base class, who is responsible for creating it? The answer, as it turns out, is Copier. The Copier constructor is responsible for creating PoweredDevice. Consequently, this is one time when Copier is allowed to call a non-immediate-parent constructor directly:
01class PoweredDevice
02{
03public:
04    PoweredDevice(int nPower)
05    {
06        cout << "PoweredDevice: " << nPower << endl;
07    }
08};
09 
10class Scanner: virtual public PoweredDevice
11{
12public:
13    Scanner(int nScanner, int nPower)
14        : PoweredDevice(nPower)
15    {
16        cout << "Scanner: " << nScanner << endl;
17    }
18};
19 
20class Printer: virtual public PoweredDevice
21{
22public:
23    Printer(int nPrinter, int nPower)
24        : PoweredDevice(nPower)
25    {
26        cout << "Printer: " << nPrinter << endl;
27    }
28};
29 
30class Copier: public Scanner, public Printer
31{
32public:
33    Copier(int nScanner, int nPrinter, int nPower)
34        : Scanner(nScanner, nPower), Printer(nPrinter, nPower), PoweredDevice(nPower)
35    {
36    }
37};
This time, our previous example:
1int main()
2{
3    Copier cCopier(1, 2, 3);
4}
produces the result:
PoweredDevice: 3
Scanner: 1
Printer: 2
As you can see, PoweredDevice only gets constructed once.
There are a few details that we would be remiss if we did not mention.
First, virtual base classes are created before non-virtual base classes, which ensures all bases get created before their derived classes.
Second, note that the Scanner and Printer constructors still have calls to the PoweredDevice constructor. If we are creating an instance of Copier, these constructor calls are simply ignored because Copier is responsible for creating the PoweredDevice, not Scanner or Printer. However, if we were to create an instance of Scanner or Printer, the virtual keyword is ignored, those constructor calls would be used, and normal inheritance rules apply.
Third, if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class. In this case, Copier inherits Printer and Scanner, both of which have a PoweredDevice virtual base class. Copier, the most derived class, is responsible for creation of PoweredDevice. Note that this is true even in a single inheritance case: if Copier was singly inherited from Printer, and Printer was virtually inherited from PoweredDevice, Copier is still responsible for creating PoweredDevice.

No comments: