[ Team LiB ] |
![]() ![]() |
Gotcha #79: Dominance IssuesYou may wonder how you ended up programming in a language that includes concepts like friends, private parts, bound friends, and dominance. In this item we'll examine the concept of dominance in hierarchy design, why it's weird, and why it's sometimes necessary. Oh, it's easy enough to claim that you lead your life in such a way that this manner of issue is never a concern, but sooner or later most expert C++ programmers find themselves face to face with a dominance situation—whether their own or one of their colleagues'—and it's best to be prepared. Forewarned is forearmed. Dominance becomes an issue only in the context of virtual inheritance and is best illustrated graphically. In Figure 7-11, the identifier B::name dominates A::name if A is a base class of B. Note that this dominance extends to other lookup paths. For example, if the compiler looks up the identifer name in the scope of class D, it will find both B::name and, through a different path, A::name. However, because of dominance, no ambiguity arises. The identifier B::name dominates. Figure 7-11. The identifier B::name dominates A::name.Note that the analogous case without virtual inheritance will result in an ambiguity. In Figure 7-12, the lookup of name in the scope of D is ambiguous, because B::name doesn't dominate the A::name in the base class of C. Figure 7-12. No dominance here. The identifier B::name hides A::name on one path but not on the other.This may seem like an odd language rule, but, without dominance, it would be impossible in many cases to construct virtual function tables for classes that use virtual inheritance. In short, the combination of dynamic binding and virtual inheritance implies the dominance rule. Let's look at a simple virtual-inheritance hierarchy, as in Figure 7-13. We can represent the storage layout of a D object as containing three base class subobjects, with pointers providing access to the shared V subobject, as shown in Figure 7-14. (Many implementations are possible. This one is a bit dated but is easy to draw and is logically equivalent to other approaches.) Figure 7-13. The function D::f overrides both B1::f and V::f. Virtual tables for B1 and V subobjects contain information to call D::f.Figure 7-14. Possible layout of a D complete object, showing the virtual function table for the V subobject.As one might expect, the declaration of the member function D::f, shown in Figure 7-13, overrides both B1::f and V::f: B2 *b2p = new D; b2p->f(); // calls D::f Let's examine another case, shown in Figure 7-15. This case is simply illegal, because either B1::f and B2::f could be used to override V::f in D. It's ambiguous and results in a compile-time error. Figure 7-15. An ambiguity. Either B1::f or B2::f could override V::f in the V subobject's virtual table.Finally, let's examine a case where dominance comes into play: B2 *b2p = new D; b2p->f(); // calls B1::f()! As Figure 7-16 shows, the identifier B::f dominates the identifier V::f on all paths, and the virtual table for the V subobject of a D object will be set to B1::f. Without the dominance rule, this case would also be ambiguous, since the implementation of V::f in a D object could be either V::f or B1::f. Dominance settles the ambiguity in favor of B1::f. Figure 7-16. Dominance disambiguates virtual table construction. B1::f dominates V::f, so the V subobject's virtual table contains information to call B1::f. |
[ Team LiB ] |
![]() ![]() |