Under single inheritance, the scope of a derived class is nested within the scope of its direct and indirect base classes (§ 15.6, p. 617). Lookup happens by searching up the inheritance hierarchy until the given name is found. Names defined in a derived class hide uses of that name inside a base.
Under multiple inheritance, this same lookup happens simultaneously among all the direct base classes. If a name is found through more than one base class, then use of that name is ambiguous.
Exercise 18.23: Using the hierarchy in exercise 18.22 along with class D
defined below, and assuming each class defines a default constructor, which, if any, of the following conversions are not permitted?
class D : public X, public C { ... };
D *pd = new D;
(a) X *px = pd;
(b) A *pa = pd;
(c) B *pb = pd;
(d) C *pc = pd;
Exercise 18.24: On page 807 we presented a series of calls made through a Bear
pointer that pointed to a Panda
object. Explain each call assuming we used a ZooAnimal
pointer pointing to a Panda
object instead.
Exercise 18.25: Assume we have two base classes, Base1
and Base2
, each of which defines a virtual member named print
and a virtual destructor. From these base classes we derive the following classes, each of which redefines the print
function:
class D1 : public Base1 { /* ... */ };
class D2 : public Base2 { /* ... */ };
class MI : public D1, public D2 { /* ... */ };
Using the following pointers, determine which function is used in each call:
Base1 *pb1 = new MI;
Base2 *pb2 = new MI;
D1 *pd1 = new MI;
D2 *pd2 = new MI;
(a) pb1->print();
(b) pd1->print();
(c) pd2->print();
(d) delete pb2;
(e) delete pd1;
(f) delete pd2;
In our example, if we use a name through a Panda
object, pointer, or reference, both the Endangered
and the Bear/ZooAnimal
subtrees are examined in parallel. If the name is found in more than one subtree, then the use of the name is ambiguous. It is perfectly legal for a class to inherit multiple members with the same name. However, if we want to use that name, we must specify which version we want to use.
When a class has multiple base classes, it is possible for that derived class to inherit a member with the same name from two or more of its base classes. Unqualified uses of that name are ambiguous.
For example, if both ZooAnimal
and Endangered
define a member named max_weight
, and Panda
does not define that member, this call is an error:
double d = ying_yang.max_weight();
The derivation of Panda
, which results in Panda
having two members named max_weight
, is perfectly legal. The derivation generates a potential ambiguity. That ambiguity is avoided if no Panda
object ever calls max_weight
. The error would also be avoided if each call to max_weight
specifically indicated which version to run—ZooAnimal::max_weight
or Endangered::max_weight
. An error results only if there is an ambiguous attempt to use the member.
The ambiguity of the two inherited max_weight
members is reasonably obvious. It might be more surprising to learn that an error would be generated even if the two inherited functions had different parameter lists. Similarly, it would be an error even if the max_weight
function were private
in one class and public
or protected
in the other. Finally, if max_weight
were defined in Bear
and not in ZooAnimal
, the call would still be in error.
As always, name lookup happens before type checking (§ 6.4.1, p. 234). When the compiler finds max_weight
in two different scopes, it generates an error noting that the call is ambiguous.
The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity. For example, we should give our Panda
class a max_weight
function that resolves the ambiguity:
double Panda::max_weight() const
{
return std::max(ZooAnimal::max_weight(),
Endangered::max_weight());
}
Exercise 18.26: Given the hierarchy in the box on page 810, why is the following call to print
an error? Revise MI
to allow this call to print
to compile and execute correctly.
MI mi;
mi.print(42);
Exercise 18.27: Given the class hierarchy in the box on page 810 and assuming we add a function named foo
to MI as follows:
int ival;
double dval;
void MI::foo(double cval)
{
int dval;
// exercise questions occur here
}
(a) List all the names visible from within MI::foo
.
(b) Are any names visible from more than one base class?
(c) Assign to the local instance of dval
the sum of the dval
member of Base1
and the dval
member of Derived
.
(d) Assign the value of the last element in MI::dvec
to Base2::fval
.
(e) Assign cval
from Base1
to the first character in sval
from Derived
.