转 Inheritance — Multiple and Virtual Inheritance
文章目录
- 转 [Inheritance — Multiple and Virtual Inheritance][1]
- Things you have to consider when you make design decisions
- What does it mean to “delegate to a sister class” via virtual inheritance
- What special considerations do I need to know about when I use virtual inheritance?
- What special considerations do I need to know about when I inherit from a class that uses virtual inheritance?
- What special considerations do I need to know about when I use a class that uses virtual inheritance?
- One more time: what is the exact order of constructors in a multiple and/or virtual inheritance situation?
- What is the exact order of destructors in a multiple and/or virtual inheritance situation?
Things you have to consider when you make design decisions
- Grow Gracefully: Does the size of the code-base grow gracefully when you add a new class type.
- Low Code Bulk: Is there a reasonably small amount of code bulk? This usually is proportional to ongoing maintenance cost — the more code the more cost, all other things being equal. It is also usually related to the “Grow Gracefully” criteria: in addition to the code bulk of the framework proper, best case there would be N+M chunks of code, worst case there would be N×M chunks of code.
- Fine Grained Control: Do you have fine granular control over the algorithms and data structures?
- Static Detect Bad Combos: Can you statically (“at compile time”) detect and prevent invalid combinations
- Polymorphic on Both Sides: Does it let users treat either base class polymorphically?
- Share Common Code: Does it let new combinations share common code from either side?
What does it mean to “delegate to a sister class” via virtual inheritance
Consider the folllowing example:
class Base {
public:
virtual void foo() = 0;
virtual void bar() = 0;
};
class Der1 : public virtual Base {
public:
virtual void foo();
};
void Der1::foo()
{ bar(); }
class Der2 : public virtual Base {
public:
virtual void bar();
};
class Join : public Der1, public Der2 {
public:
// ...
};
int main()
{
Join* p1 = new Join();
Der1* p2 = p1;
Base* p3 = p1;
p1->foo();
p2->foo();
p3->foo();
}
Believe it or not, when Der1::foo() calls this->bar(), it ends up calling Der2::bar(). Yes, that’s right: a class that Der1 knows nothing about will supply the override of a virtual function invoked by Der1::foo(). This “cross delegation” can be a powerful technique for customizing the behavior of polymorphic classes.
What special considerations do I need to know about when I use virtual inheritance?
Generally, virtual base classes are most suitable when the classes that derive from the virtual base, and especially the virtual base itself, are pure abstract classes. This means the classes above the “join class” have very little if any data.
Note: even if the virtual base itself is a pure abstract class with no member data, you still probably don’t want to remove the virtual inheritance within classes Der1 and Der2. You can use fully qualified names to resolve any ambiguities that arise, and you might even be able to squeeze out a few cycles in some cases, however the object’s address is somewhat ambiguous (there are still two Base class subobjects in the Join object), so simple things like trying to find out if two pointers point at the same instance might be tricky. Just be careful — very careful.
What special considerations do I need to know about when I inherit from a class that uses virtual inheritance?
Initialization list of most-derived-class’s ctor directly invokes the virtual base class’s ctor.
Because a virtual base class subobject occurs only once in an instance, there are special rules to make sure the virtual base class’s constructor and destructor get called exactly once per instance. The C++ rules say that virtual base classes are constructed before all non-virtual base classes. The thing you as a programmer need to know is this: constructors for virtual base classes anywhere in your class’s inheritance hierarchy are called by the “most derived” class’s constructor.
Practically speaking, this means that when you create a concrete class that has a virtual base class, you must be prepared to pass whatever parameters are required to call the virtual base class’s constructor. And, of course, if there are several virtual base classes anywhere in your classes ancestry, you must be prepared to call all their constructors. This might mean that the most-derived class’s constructor needs more parameters than you might otherwise think.
However, if the author of the virtual base class followed the guideline in the previous FAQ, then the virtual base class’s constructor probably takes no parameters since it doesn’t have any data to initialize. This means (fortunately!) the authors of the concrete classes that inherit eventually from the virtual base class do not need to worry about taking extra parameters to pass to the virtual base class’s ctor.
What special considerations do I need to know about when I use a class that uses virtual inheritance?
No C-style downcasts; use dynamic_cast instead.
One more time: what is the exact order of constructors in a multiple and/or virtual inheritance situation?
The very first constructors to be executed are the virtual base classes anywhere in the hierarchy. They are executed in the order they appear in a depth-first left-to-right traversal of the graph of base classes, where left to right refer to the order of appearance of base class names.
After all virtual base class constructors are finished, the construction order is generally from base class to derived class. The details are easiest to understand if you imagine that the very first thing the compiler does in the derived class’s ctor is to make a hidden call to the ctors of its non-virtual base classes (hint: that’s the way many compilers actually do it). So if class D inherits multiply from B1 and B2, the constructor for B1 executes first, then the constructor for B2, then the constructor for D. This rule is applied recursively; for example, if B1 inherits from B1a and B1b, and B2 inherits from B2a and B2b, then the final order is B1a, B1b, B1, B2a, B2b, B2, D.
Note that the order B1 and then B2 (or B1a then B1b) is determined by the order that the base classes appear in the declaration of the class, not in the order that the initializer appears in the derived class’s initialization list.
What is the exact order of destructors in a multiple and/or virtual inheritance situation?
Short answer: the exact opposite of the constructor order.
Long answer: suppose the “most derived” class is D, meaning the actual object that was originally created was of class D, and that D inherits multiply (and non-virtually) from B1 and B2. The sub-object corresponding to most-derived class D runs first, followed by the dtors for its non-virtual base classes in reverse declaration-order. Thus the destructor order will be D, B2, B1. This rule is applied recursively; for example, if B1 inherits from B1a and B1b, and B2 inherits from B2a and B2b, the final order is D, B2, B2b, B2a, B1, B1b, B1a.
After all this is finished, virtual base classes that appear anywhere in the hierarchy are handled. The destructors for these virtual base classes are executed in the reverse order they appear in a depth-first left-to-right traversal of the graph of base classes, where left to right refer to the order of appearance of base class names. For instance, if the virtual base classes in that traversal order are V1, V1, V1, V2, V1, V2, V2, V1, V3, V1, V2, the unique ones are V1, V2, V3, and the final-final order is D, B2, B2b, B2a, B1, B1b, B1a, V3, V2, V1.
Reminder to make your base class’s destructor virtual, at least in the normal case. If you don’t thoroughly understand the rules for why you make your base class’s destructor virtual, then either learn the rationale or just trust me and make them virtual.