Last chapter, in lesson 17.9 -- Multiple inheritance, we left off talking about the “diamond problem”. In this section, we will resume this discussion.
上一章,在多继承里面我们留下了一些关于菱形问题的讨论,在本节中,我们会继续这些讨论。
Note: This section is an advanced topic and can be skipped or skimmed if desired.
注意这一节是高级话题。如果你想的话,你可以跳过。
Here is our example from the previous lesson (with some constructors) illustrating the diamond problem:
这个是我们之前课程中的例子,表述了一个菱形问题。
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: public PoweredDevice
{
public:
Scanner(int scanner, int power)
: PoweredDevice{ power }
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: public PoweredDevice
{
public:
Printer(int printer, int power)
: PoweredDevice{ power }
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: Scanner{ scanner, power }, Printer{ printer, power }
{
}
};
Although you might expect to get an inheritance diagram that looks like this:
你可能会期望得到一个继承图示:
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:
如果你想创建一个复印机对象,通常情况为下,你会得到PoweredDevice的两份拷贝。
We can create a short example that will show this in action:
我们可以写一点代码:
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}
This produces the result:
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
As you can see, PoweredDevice got constructed twice.
你可以看到PoweredDevice被构造了两次。
While this is often desired, other times you may want only one copy of PoweredDevice to be shared by both Scanner and Printer.
尽管经常需要这样做,但是你可能想只要一份PoweredDevice的拷贝,共享给Scanner和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. The base object is shared between all objects in the inheritance tree and it is only constructed once. Here is an example (without constructors for simplicity) showing how to use the virtual keyword to create a shared base class:
要共享一个基类,简单的在继承列表的前面插入一个virtual关键字就可以。这样创建了一个虚基类,意味着只有一个基类对象。这个基类对象被所有的继承树上的对象所共享,而且只被创建一次。
class PoweredDevice
{
};
class Scanner: virtual public PoweredDevice
{
};
class Printer: virtual public PoweredDevice
{
};
class Copier: public Scanner, public Printer
{
};
Now, when you create a Copier class object, you will get only one copy of PoweredDevice per Copier that will be shared by both Scanner and Printer.
现在的话,你在创建一个打印机对象的时候,你就只会得到一份PoweredDevice
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:
然而,这又会导致一个问题:如果Scanner和Printer共享一个PoweredDevice,谁来负责创建它呢?答案是,复印机创建这个PoweredDevice,所以Copier是要对PoweredDevice的创建负责的。所以这一次允许复印机直接调用非直接父类的构造函数。
#include <iostream>
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Scanner(int scanner, int power)
: PoweredDevice{ power } // this line is required to create Scanner objects, but ignored in this case
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Printer(int printer, int power)
: PoweredDevice{ power } // this line is required to create Printer objects, but ignored in this case
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: PoweredDevice{ power }, // PoweredDevice is constructed here
Scanner{ scanner, power }, Printer{ printer, power }
{
}
};
This time, our previous example:
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}
produces the result:
PoweredDevice: 3
Scanner: 1
Printer: 2
As you can see, PoweredDevice only gets constructed once.
你可以看到,PoweredDevice只被构造了一次。
There are a few details that we would be remiss if we did not mention.
这里有一些我们不提的话你就会略过的细节。
First, virtual base classes are always 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. When 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, those constructor calls would be used, and normal inheritance rules apply.
第二注意到Scanner和Printer的构造函数中仍然有对PoweredDeviced构造函数的采用。当创建Copier的实例的时候,这三个构造函数被简单的忽视了,因为Copier对创建PoweredDevice负责,不是Scanner也不是Printter。然而,如果我们创建一个Scanner对象或者创建一个Printer对象的时候,他们的构造函数会被调用,普通的继承规则在此时适用。
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.
第三,如果一个类继承了一个或多个类,那些类有虚基类的父类。最派生的类负责构造虚基类。在本例中,Copier继承了Printer和Scanner,他们两都有一个PoeredDevice是一个虚基类。Copier作为最派生的类,对虚基类PoweredDevice的创建是有责任的,主要这个规则对于单继承也是适用的。如果Copier仅仅继承了Printer,Printer从PoweredDevice虚拟的继承了,Copier仍然需要对PoweredDevice的创建负责。
Fourth, all classes inheriting a virtual base class will have a virtual table, even if they would normally not have one otherwise, and thus be larger by a pointer.
第四点,所有继承了虚基类的类都会有一个虚表,即便他们他们不会有一个vptr,也不会因此增大一个指针的大小。
Because Scanner and Printer derive virtually from PoweredDevice, Copier will only be one PoweredDevice subobject. Scanner and Printer both need to know how to find that single PoweredDevice subobject, so they can access its members (because after all, they are derived from it). This is typically done through some virtual table magic (which essentially stores the offset from each subclass to the PoweredDevice subobject).
因为Scanner和Printer虚拟的从PoweredDevice继承,Copier只会有一份PoweredDevice的自对的。Scanner和Printer都需要知道怎么找到那个子对象,他们才可以访问PoweredDevice的成员(毕竟,他们从PoweredDevice派生出来,他们理应可以访问)。而这是通过一些虚表的黑魔法来实现的,实质上是存储了每个子类到虚表子对象的偏移。