Session 7: Inheritance in C++

Inheritance (revision)

Inheritance in C++

The basic concept is similar to Java, but

Inheritance syntax in Java and C++

A base class

Recall the class Date from week 2:
    class Date {
            int day, month, year;

    public:
            Date();         // today's date
            Date(int d, int m);
            Date(int d, int m, int y);
            int get_day() const { return day; }
            int get_month() const { return month; }
            int get_year() const { return year; }
    };

Inheritance and initialization

The members of base class(es) are initialized similarly to subobjects:

    class Holiday : public Date {
            string name;
    public:
            Holiday(string n) : Date(), name(n) {}

            Holiday(string n, int d, int m) :
                    Date(d, m), name(n) {}

            string get_name() const { return name; }
    };
Members of the base class can't be initialized directly: use Date.

Order of initialization

Initialization is done in the following order:

  1. constructors for base classes
  2. members (in order of declaration)
  3. body of constructor
The order in which initializers are given has no effect, so might as well follow the order of declaration.

Initialization and assignment

Method overriding in Java and C++

The default in C++ is the opposite to that in Java:

Method overriding

Overridable methods must be declared virtual:

    class Date {
            ...
            virtual string desc() const { ... }
    };
overriding in a derived class:
    class Holiday : public Date {
            ...
            virtual string desc() const {
                    return name + " " + Date::desc();
            }
    };
Note: qualify with the class name to get the base version.

Static and dynamic binding

Given functions
    void print_day1(Date d) {
            cout << "It's " << d.desc() << '\n';
    }

    void print_day2(Date &d) {
            cout << "It's " << d.desc() << '\n';
    }
then
    Holiday xmas("Christmas", 25, 12);
    print_day1(xmas);        // It's 25/12/2004
    print_day2(xmas);        // It's Christmas 25/12/2004

Abstract classes

A class containing a pure virtual function is abstract, though this is not marked in the syntax.
    class Pet {
    protected:
        string _name;
    public:
        Pet(string name) : _name(name) {}
        virtual string sound() const = 0;
        virtual void speak() const {
            cout << _name << ": " << sound() << "!\n";
        }
    };
As in Java, abstract classes may not be instantiated, so no variable may have type Pet, but we can declare a reference.

Derived classes

    class Dog : public Pet {
    public:
        Dog(string name) : Pet(name) {}
        string sound() const { return "woof"; }
        void speak() const {      // virtual is optional
            Pet::speak();
            cout << '(' << _name << " wags tail)\n";
        }
    };

    class Cat : public Pet {
    public:
        Cat(string name) : Pet(name) {}
        virtual string sound() const { return "miao"; }
    };

Subtype polymorphism and dynamic binding

We cannot pass Pets by value, but we can pass them by reference:
    void speakTwice(const Pet &pet) {
        pet.speak();
        pet.speak();
    }
Then we can write
    Dog a_dog("Fido", 30);
    speakTwice(a_dog);
    Cat a_cat("Tiddles");
    speakTwice(a_cat);

Caution: inheritance and overloading

    class A {
        virtual f(int n, Point p) { ... }
    }
Now suppose we intend to override f in a derived class, but make a mistake with the argument types:
    class B : public A {
        f(Point p, int n) { ... }
    }
This will be accepted as a definition of a new and different member function.

Which version is selected?

If more than one overloaded function or method matches, the best (most specific) is chosen:
    class Pet {};
    class Cat : public Pet {};

    void wash(Pet &x) { ... }
    void wash(Cat &x) { ... }

    int main() {
        Cat felix;
        wash(felix); // both functions match; second is used
    }

Ambiguity

The following is rejected by the the compiler:
    class Pet {};
    class Dog : public Pet {};
    class Cat : public Pet {};

    void chase(Pet &x, Cat &y) { ... }
    void chase(Dog &x, Pet &y) { ... }

    int main() {
        Dog buster;
        Cat tom;
        chase(buster, tom);   // ambiguous!
    }

Pointers and subtyping

Pointers to derived classes are subtypes of pointers to base classes:
    Cat felix;
    Pet *p = &felix;
Because the pointer is copied (not the object) no slicing occurs:
    p->speak();         // miao
The speak method uses the virtual method sound, which is defined in the Cat class, and selected by dynamic binding.

Containers of pointers

Often a container holds pointers to a base type:

    vector<Pet *> pets;
    Cat felix("Felix");
    Dog fido("Fido");
    pets.push_back(&felix);
    pets.push_back(&fido);
When we access elements of the vector, dynamic binding is used:
    for (int i = 0; i < pets.size(); i++)
        pet[i]->speak(); // miao, woof

Introducing dynamic allocation

Templates and subtyping (I)

If Cat is a subtype of Pet,

See Stroustrup 13.6.3 for more.

Templates and subtyping (II)

Next week: multiple inheritance