Tuesday 23 October 2012

Derivation and Inheritance, Multiple Inheritance and Virtual Functions

1.2 Derivation and Inheritance
1.2.1 Derivation
Inheritance is implemented in C++ through the mechanism of derivation. Derivation allows you to derive a class, called a derived class, from another class, called a base class.
clip_image001clip_image001[1]Derived class syntax:
image

In the declaration of a derived class, you list the base classes of the derived class. The derived class inherits its members from these base classes.
The qualified_class_specifier must be a class that has been previously declared in a class declaration.

An access specifier is one of public, private, or protected.
The virtual keyword can be used to declare virtual base classes.
The following example shows the declaration of the derived class D and the base classes V, B1, and B2. The class B1 is both a base class and a derived class because it is derived from class V and is a base class for D:
class V { /* ... */ };
class B1 : virtual public V { /* ... */ };
class B2 { /* ... */ };
class D : public B1, private B2 { /* ... */ };
Classes that are declared but not defined are not allowed in base lists.
For example:
class X;
// error
class Y: public X { };

The compiler will not allow the declaration of class Y because X has not been defined.
When you derive a class, the derived class inherits class members of the base class. You can refer to inherited members (base class members) as if they were members of the derived class. For example:
class Base {
public:
int a,b;
};
class Derived : public Base {
public:
int c;
};
int main() {
Derived d;
d.a = 1; // Base::a
d.b = 2; // Base::b
d.c = 3; // Derived::c
}

The derived class can also add new class members and redefine existing base class members. In the above example, the two inherited members, a and b, of the derived class d, in addition to the derived class member c, are assigned values. If you redefine base class members in the derived class, you can still refer to the base class members by using the :: (scope resolution) operator. For example:
#include <iostream>
using namespace std;
class Base {
public:
char* name;
void display() {
cout << name << endl;
}
};
class Derived: public Base {
public:
char* name;
void display() {
cout << name << ", " << Base::name << endl;
}
};
int main() {
Derived d;
d.name = "Derived Class";
d.Base::name = "Base Class";
// call Derived::display()
d.display();
// call Base::display()
d.Base::display();
}
Output:
Derived Class, Base Class
Base Class

You can manipulate a derived class object as if it were a base class object. You can use a pointer or a reference to a derived class object in place of a pointer or reference to its base class. For example, you can pass a pointer or reference to a derived class object D to a function expecting a pointer or reference to the base class of D. You do not need to use an explicit cast to achieve this; a standard conversion is performed. You can implicitly convert a pointer to a derived class to point to an accessible unambiguous base class. You can also implicitly convert a reference to a derived class to a reference to a base class.
The following example demonstrates a standard conversion from a pointer to a derived class to a pointer to a base class:
#include <iostream>
using namespace std;
class Base {
public:
char* name;
void display() {
cout << name << endl;
}
};
class Derived: public Base {
public:
char* name;
void display() {
cout << name << ", " << Base::name << endl;
}
};
int main() {
Derived d;
d.name = "Derived Class";
d.Base::name = "Base Class";
Derived* dptr = &d;
// standard conversion from Derived* to Base*
Base* bptr = dptr;
// call Base::display()
bptr->display();
}

Output:
Base Class
The statement Base* bptr = dptr converts a pointer of type Derived to a pointer of type Base.
The reverse case is not allowed. You cannot implicitly convert a pointer or a reference to a base class object to a pointer or reference to a derived class. For example, the compiler will not allow the following code if the classes Base and Class are defined as in the above example:
int main() {
Base b;
b.name = "Base class";
Derived* dptr = &b;
}

The compiler will not allow the statement Derived* dptr = &b because the statement is trying to implicitly convert a pointer of type Base to a pointer of type Derived.
If a member of a derived class and a member of a base class have the same name, the base class member is hidden in the derived class. If a member of a derived class has the same name as a base class, the base class name is hidden in the derived class.

1.2.2 Inheritance
Inheritance is a mechanism of reusing and extending existing classes without modifying them, thus producing hierarchical relationships between them.
Inheritance is almost like embedding an object into a class. Suppose that you declare an object x of class A in the class definition of B. As a result, class B will have access to all the public data members and member functions of class A. However, in class B, you have to access the data members and member functions of class A through object x. The following example demonstrates this:
#include <iostream>
using namespace std;
class A {
int data;
public:
void f(int arg) { data = arg; }
int g() { return data; }
};
class B {
public:
A x;
};
int main() {
B obj;
obj.x.f(20);
cout << obj.x.g() << endl;
// cout << obj.g() << endl;
}

In the main function, object obj accesses function A::f() through its data member B::x with the statement obj.x.f(20). Object obj accesses A::g() in a similar manner with the statement obj.x.g(). The compiler would not allow the statement obj.g() because g() is a member function of class A, not
class B.

The inheritance mechanism lets you use a statement like obj.g() in the above example. In order for that statement to be legal, g() must be a member function of class B.
Inheritance lets you include the names and definitions of another class's members as part of a new class. The class whose members you want to include in your new class is called a base class. Your new class is derived from the base class. The new class contains a subobject of the type of the base class. The following example is the same as the previous example except it uses the inheritance mechanism to give class B access to the members of

class A:
#include <iostream>
using namespace std;
class A {
int data;
public:
void f(int arg) { data = arg; }
int g() { return data; }
};
class B : public A { };
int main() {
B obj;
obj.f(20);
cout << obj.g() << endl;
}

Class A is a base class of class B. The names and definitions of the members of class A are included in the definition of class B; class B inherits the members of class A. Class B is derived from class A. Class B contains a subobject of type A.
You can also add new data members and member functions to the derived class. You can modify the implementation of existing member functions or data by overriding base class member functions or data in the newly derived class.
You may derive classes from other derived classes, thereby creating another level of inheritance. The following example demonstrates this:
struct A { };
struct B : A { };
struct C : B { };

Class B is a derived class of A, but is also a base class of C. The number of levels of inheritance is only limited by resources.
Multiple inheritance allows you to create a derived class that inherits properties from more than one base class. Because a derived class inherits members from all its base classes, ambiguities can result. For example, if two base classes have a member with the same name, the derived class cannot implicitly differentiate between the two members. Note that, when you are using multiple inheritance, the access to names of base classes may be ambiguous. See Multiple inheritance section for more detailed information.
A direct base class is a base class that appears directly as a base specifier in the declaration of its derived class.
An indirect base class is a base class that does not appear directly in the declaration of the derived class but is available to the derived class through one of its base classes. For a given class, all base classes that are not direct base classes are indirect base classes. The following example demonstrates direct and indirect base classes:

class A {
public:
int x;
};
class B : public A {
public:
int y;
};

class C : public B { };

Class B is a direct base class of C. Class A is a direct base class of B. Class A is an indirect base class of C. (Class C has x and y as its data members.)
Polymorphic functions are functions that can be applied to objects of more than one type. In C++, polymorphic functions are implemented in two ways:
· Overloaded functions are statically bound at compile time.
· C++ provides virtual functions. A virtual function is a function that can be called for a number of different user-defined types that are related through derivation. Virtual functions are bound dynamically at run time. They are described in more detail a little further in the chapter.
Self Assessment Questions
1. Explain the derived class syntax.
2. What do you mean by direct and indirect base classes?
1.3 Multiple Inheritance
You can derive a class from any number of base classes. Deriving a class from more than one direct base class is called multiple inheritance.
In the following example, classes A, B, and C are direct base classes for the derived class X:
class A { /* ... */ };
class B { /* ... */ };
class C { /* ... */ };
class X : public A, private B, public C { /* ... */ };
The following inheritance graph describes the inheritance relationships of the above example. An arrow points to the direct base class of the class at the tail of the arrow:
clip_image003
The order of derivation is relevant only to determine the order of default initialization by constructors and cleanup by destructors.
A direct base class cannot appear in the base list of a derived class more than once:
class B1 { /* ... */ }; // direct base class
class D : public B1, private B1 { /* ... */ }; // error
However, a derived class can inherit an indirect base class more than once, as shown in the following example:
clip_image005
class L { /* ... */ }; // indirect base class
class B2 : public L { /* ... */ };
class B3 : public L { /* ... */ };
class D : public B2, public B3 { /* ... */ }; // valid
In the above example, class D inherits the indirect base class L once through class B2 and once through class B3. However, this may lead to ambiguities because two subobjects of class L exist, and both are accessible through class D. You can avoid this ambiguity by referring to class L using a qualified class name. For example:
B2::L
or
B3::L.
You can also avoid this ambiguity by using the base specifier virtual to declare a base class, as described in Section 1.2.1.

1.4 Virtual Functions
By default, C++ matches a function call with the correct function definition at compile time. This is called static binding. You can specify that the compiler match a function call with the correct function definition at run time; this is called dynamic binding. You declare a function with the keyword virtual if you want the compiler to use dynamic binding for that specific function.
The following examples demonstrate the differences between static and dynamic binding. The first example demonstrates static binding:
#include <iostream>
using namespace std;
struct A {
void f() { cout << "Class A" << endl; }
};
struct B: A {
void f() { cout << "Class B" << endl; }
};
void g(A& arg) {
arg.f();
}
int main() {
B x;
g(x);
}
The following is the output of the above example:
Class A
When function g() is called, function A::f() is called, although the argument refers to an object of type B. At compile time, the compiler knows only that the argument of function g() will be a reference to an object derived from A; it cannot determine whether the argument will be a reference to an object of type A or type B. However, this can be determined at run time. The following example is the same as the previous example, except that A::f() is declared with the virtual keyword:
#include <iostream>
using namespace std;
struct A {
virtual void f() { cout << "Class A" << endl; }
};
struct B: A {
void f() { cout << "Class B" << endl; }
};
void g(A& arg) {
arg.f();
}
int main() {
B x;
g(x);
}
The following is the output of the above example:
Class B
The virtual keyword indicates to the compiler that it should choose the appropriate definition of f() not by the type of reference, but by the type of object that the reference refers to.
Therefore, a virtual function is a member function you may redefine for other derived classes, and can ensure that the compiler will call the redefined virtual function for an object of the corresponding derived class, even if you call that function with a pointer or reference to a base class of the object.
A class that declares or inherits a virtual function is called a polymorphic class.
You redefine a virtual member function, like any member function, in any derived class. Suppose you declare a virtual function named f in a class A, and you derive directly or indirectly from A a class named B. If you declare a function named f in class B with the same name and same parameter list as A::f, then B::f is also virtual (regardless whether or not you declare B::f with the virtual keyword) and it overrides A::f. However, if the parameter lists of A::f and B::f are different, A::f and B::f are considered different, B::f does not override A::f, and B::f is not virtual (unless you have declared it with the virtual keyword). Instead B::f hides A::f. The following example demonstrates this:
#include <iostream>
using namespace std;
struct A {
virtual void f() { cout << "Class A" << endl; }
};
struct B: A {
void f(int) { cout << "Class B" << endl; }
};
struct C: B {
void f() { cout << "Class C" << endl; }
};
int main() {
B b; C c;
A* pa1 = &b;
A* pa2 = &c;
// b.f();
pa1->f();
pa2->f();
}

The following is the output of the above example:
Class A
Class C
The function B::f is not virtual. It hides A::f. Thus the compiler will not allow the function call b.f(). The function C::f is virtual; it overrides A::f even though A::f is not visible in C.
If you declare a base class destructor as virtual, a derived class destructor will override that base class destructor, even though destructors are not inherited.
The return type of an overriding virtual function may differ from the return type of the overridden virtual function. This overriding function would then be called a covariant virtual function. Suppose that B::f overrides the virtual function A::f. The return types of A::f and B::f may differ if all the following conditions are met:
· The function B::f returns a reference or pointer to a class of type T, and A::f returns a pointer or a reference to an unambiguous direct or indirect base class of T.
· The const or volatile qualification of the pointer or reference returned by B::f has the same or less const or volatile qualification of the pointer or reference returned by A::f.
· The return type of B::f must be complete at the point of declaration of B::f, or it can be of type B.

The following example demonstrates this:
#include <iostream>
using namespace std;
struct A { };
class B : private A {
friend class D;
friend class F;
};
A global_A;
B global_B;
struct C {
virtual A* f() {
cout << "A* C::f()" << endl;
return &global_A;
}
};
struct D : C {
B* f() {
cout << "B* D::f()" << endl;
return &global_B;
}
};
struct E;
struct F : C {
// Error:
// E is incomplete
// E* f();
};
struct G : C {
// Error:
// A is an inaccessible base class of B
// B* f();
};
int main() {
D d;
C* cp = &d;
D* dp = &d;
A* ap = cp->f();
B* bp = dp->f();
};

The following is the output of the above example:
B* D::f()
B* D::f()

The statement A* ap = cp->f() calls D::f() and converts the pointer returned to type A*. The statement B* bp = dp->f() calls D::f() as well but does not convert the pointer returned; the type returned is B*. The compiler would not allow the declaration of the virtual function F::f() because E is not a complete class. The compiler would not allow the declaration of the virtual function G::f() because class A is not an accessible base class of B (unlike friend classes D and F, the definition of B does not give access to its members for class G).
A virtual function cannot be global or static because, by definition, a virtual function is a member function of a base class and relies on a specific object to determine which implementation of the function is called. You can declare a virtual function to be a friend of another class.

If a function is declared virtual in its base class, you can still access it directly using the scope resolution (::) operator. In this case, the virtual function call mechanism is suppressed and the function implementation defined in the base class is used. In addition, if you do not override a virtual member function in a derived class, a call to that function uses the function implementation defined in the base class.
A virtual function must be one of the following:
· Defined
· Declared pure
· Defined and declared pure
A base class containing one or more pure virtual member functions is called an abstract class.

Self Assessment Questions
1. What is a covariant virtual function?
2. What is a polymorphic class?

1.5 Polymorphism

In simple terms, polymorphism lets you treat derived class members just like their parent class's members.
More precisely, polymorphism is the ability of objects belonging to different types to respond to method calls of methods of the same name, each one according to an appropriate type-specific behavior. The programmer (and the program) does not have to know the exact type of the object in advance, so this behavior can be implemented at run time (this is called late binding or dynamic binding).
The different objects involved only need to present a compatible interface to the clients (the calling routines). That is, there must be public methods with the same name and the same parameter sets in all the objects. In principle, the object types may be unrelated, but since they share a common interface, they are often implemented as subclasses of the same parent class. Though it is not required, it is understood that the different methods will also produce similar results (for example, returning values of the same type).
In practical terms, polymorphism means that if class B inherits from class A, it doesn’t have to inherit everything about class A; it can do some of the things that class A does differently. This means that the same “verb” can result in different actions as appropriate for a specific class, so controlling code can issue the same command to a series of objects and get appropriately different results from each one.
Polymorphism allows client programs to be written based only on the abstract interfaces of the objects which will be manipulated (interface inheritance). This means that future extension in the form of new types of objects is easy, if the new objects conform to the original interface. In particular, with object-oriented polymorphism, the original client program does not even need to be recompiled (only relinked) in order to make use of new types exhibiting new (but interface-conformant) behavior.
(In C++, for instance, this is possible because the interface definition for a class defines a memory layout, the virtual function table describing where pointers to functions can be found. Future, new classes can work with old, precompiled code because the new classes must conform to the abstract class interface, meaning that the layout of the new class's virtual function table is the same as before; the old, precompiled code can still look at the same memory offsets relative to the start of the object's memory in order to find a pointer to the new function. It is only that the new virtual function table points to a new implementation of the functions in the table, thus allowing new, interface-compliant behavior with old, precompiled code.)
Consider the following example:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Animal {
public:
Animal(const string & name) : name(name) { }
virtual ~Animal() { }
virtual const string talk() = 0;
const string name;
};
class Cat : public Animal {
public:
Cat(const string & name) : Animal(name) { }
virtual ~Cat() { }
virtual const string talk() { return "Meow!"; }
};
class Dog : public Animal {
public:
Dog(const string & name) : Animal(name) { }
virtual ~Dog() { }
virtual const string talk() { return "Arf! Arf!"; }
};
// prints the following:
//
// Missy: Meow!
// Mr. Bojangles: Meow!
// Lassie: Arf! Arf!
int main() {
Animal * animals [] = {
new Cat("Missy"),
new Cat("Mr. Bojangles"),
new Dog("Lassie")
};
for(int i = 0; i < 3; i++)
cout << animals[i]->name << ": " << animals[i]->talk() << endl;
for(int i = 0; i < 3; i++)
delete animals[i];
return 0;
}

Note that the talk() method is explicitly declared as virtual. This is because polymorphic method calls have relatively high overhead in C++. This overhead is lessened by treating all method calls as non-polymorphic, unless explicitly marked as virtual by the developer.

No comments:

Post a Comment