The Essentials of C++, David hunter

Chapter 9. Inheritance 상속

9.1 Simple Inheritance 단순 상속

  • The inheritance mechanism allows a programmer to derive a new class type from an existing class type. Classes so created are called “derived” classes. The classes they are derived from are called “base” classes. For example, one may define a truck class to be a specific kind of vehicle.

  • 상속 메커니즘은 프로그래머가 기존 클래스 유형에서 새로운 클래스 유형을 파생할 수 있게 해줍니다. 이렇게 생성된 클래스는 “파생 클래스(derived classes)”라고 합니다. 이들이 파생된 클래스는 “기본 클래스(base classes)”라고 불립니다. 예를 들어, 프로그래머는 트럭 클래스를 차량(vehicle)의 특정 유형으로 정의할 수 있습니다. 이렇게 하면 기본 클래스인 차량 클래스에서 특성과 동작을 상속받아 트럭 클래스를 정의할 수 있습니다.

class truck : public vehicle {
public:
    truck(float in_weight, int in_wheels = 4, float max_load = 24000.0) : wheels(in_wheels), weight(in_weight), payload(max_load) {}
    float efficiency() { return payload / (payload + weight); }
    void honk() { cout << "Aooga! \n" << endl; }
protected:
    float payload;
};
  • The syntax “public vehicle” means that the truck class will inherit the variables and methods defined in the vehicle class. Thus, a truck object will have wheels, weight, and payload attributes, and wheel_loading, efficiency, and honk methods. Just as trucks are a derived class of vehicles, one could similarly define other derived classes of vehicles, derived classes of those derived classes, and so on, forming a hierarchy of data types.

  • Destructor, constructor, copy and assignment methods are not inherited to derived classes and must be explicitly created if they are desired.

  • It is frequently useful for derived classes to redefine classes to redefine a method or attribute that it is present in one of its base classes. Trucks, for example, have a distinctive honk method. When a truck object receives a honk message it wiil response by writing “Aooga!” since that is the message specifically associated with trucks. Instances of other vehicle classes that do not have specific honk methods would respond by writing “Beep Beep!” when receiving a honk message. In general, when an object receives a message it will search up through its inheritance hierarchy until it finds a matching method, then execute that method.

9.2 Construction and Destruction of Derived Class Objects

  • An alternate way of defining the truck constructor method would involve first invoking the vehicle constructor, then perform any construction steps specific to trucks. The method definition below adapts this approach to truck construction.
truck (float in_weight, int in_wheels = 4, float max_load = 24000.0) : vehicle(in_weight, in_wheels), payload(max_load) {}
  • The notation: base class(arg1, arg2, …) following a constructor means “invoke the base constructor with the specified arguments”.

  • If the base class constructor is not explicitly called in this way, the C++ compiler will automatically generate a call to the base class’s default constructor (the constructor with no parameters) before the derived class’s constructor is called. If the base class has no default constructor, the derived class will not compile. Thus, when a derived class object is created the various relevant constructor methods are executed beginning with the topmost base class and proceeding down the inheritance hierarchy for each class on the inheritance path.

  • When a derived class object is destroyed, class destructors are automatically executed in the reverse order in which the constructors were executed.

9.3 Public, Protected, and Private Keywords

  • A class definition may be divided into public, protected, and private sections. Public class members are accessible anywhere, protected members are accessible within the class and any of its derived classes, and private members are accessible only within the class itself - they are not inherited into derived classes.

  • These keywords can also be used to control the inheritance mechanisom when derived classes are created. As seen in the previous section, the usual method of specifying class derivation is to specify public base_class in the derived class definition. Using this technique, called public derivation, the public members of the base class become public members of the derived class and the protected members of the base class become protected members of the derived class. (Private members are never inherited.)

  • One may also specify protected or private derivation by writing protected base_class or private base_class after the derived class name. When protected derivation is used, the public and protected members of the base class become protected members of the derived class. Under private derivation, the public and protected members of the base class become private members of the derived class.

9.4 Multiple Inheritance

  • It is possible for a class to be derived from more than one base class. This situation is called multiple inheritance, and is illustrated by the example below.
class rental_car: public vehicle{
    public:
        rental_car (float in_rate) : daily rate(in_rate) {}
        rental_car () : daily_rate (0) {}
        float rate() { return daily_rate; }
    protected:
        float daily_rate;
};

class driver {
    public:
        driver(float in_rate) : hourly_rate(in_rate) {}
        driver() : hourly_rate(0) {}
        float rate() { return hourly_rate; }
    protected:
        float hourly_rate;
};

class chauffered_vehicle : public rental_car, public driver {
    public:
        chauffered_vehicle (float vehicle_rate, float driver_rate) : hourly_rate(driver_rate), daily_rate(vehicle_rate) {}
        float cost(float hours) 
        { 
            return hourly_rate * hours + ( (int) (hours/24+0.5) ) * daily_rate;
        };
};
  • In this example, the chauffeured_vehicle class is derived from both vehicle and driver. Any chauffeured_vehicle object contains all the methods and attributes of vehicle, driver, and chauffeured_vehicle.

  • When multiple inheritance occurs it is easily possible for the same identifier to be present in two or more of a class’s base classes. In the example above, the chauffeured_vehicle class inherits a rate() emthod from both its base classes. Given cv as a chauffeured_vehicle object, the reference cv.rate() is therefore ambiguous. The scope resolution operator is used to resolve the ambiguous references in these cases. Using this operator, one could access cv.rental_car::rate() or cv.driver::rate().

  • Similar syntax is used to resolve ambiguous references to base class components within a class definition. For example, the cost method could be written as:

float cost(float hourse)
{
    return driver::rate() * hours + ( (int) (hours/24+0.5) ) * rental_car::rate();
};

9.5 Virtual Methods and Polymorphisms

  • Given the declaration vehicle *p, it is acceptable for p to point to either a vehicle object or a truck object since trucks are a derived class of vehicles. Since vehicles and trucks have different honk() methods, one would presumably like the expression p->honk() to write either “Beep! Beep!” or “Aooga” depending upon the class of object p is pointing to. This desired behavior is called “polymorphism”.

  • However, by default, methods in C++ do not exhibit polymorphic behavior. Thus, if p is a pointer to a vehicle, and given the class definitions of vehicle and truck seen earier, the invocation p->honk() will always result in the execution of vehicle::honk() even if p happens to point to a truck object. To produce polymorphic behavior in a method, a C++ programmer must mark that method as being virtual, as shown below.

class vehicle {
    public:
    // ...
    virtual void honk()
    { cout << "Beep Beep!\n" << endl; }
    // ...
    
    protected:
    // ...
};

class truck : public vehicle {
    public:
    // ...
    virtual void honk()
    {
        cout << "Aooga! \n" << endl; 
    }
    protected:
    // ...
};
  • The invocation of virtual methods requires a great deal of run time overhead that is not required fro invoking nonvirtual methods. This is why methods are not virtual by default. Programmers should consider carefully which methods require polymorphism and which do not. Only those methods that require it should be marked as virtual.

  • The keyword virtual should appear only within a class definition. That is, if only the method’s prototype is given in the class definition, the prototype should be marked as virtual but the implementation should not be. This is illustrated on the next page.

class vehicle {
    public:
    // ...
    // declare virtual in prototype
    virtual void honk();
    // ...
    protected:
    // ...
};

// do not repeat the virtual keyword here
void vehicle::honk()
{
    cout << "Beep! Beep!\n" << endl;
}

9.6 Abstract Classes

  • A “pure virtual method” is defined using the notation:
virtual type name(arg1, arg2, ...) = 0;
  • A class with one or more pure virtual methods is called an “abstract” class. No objects of that class can be created. Abstract classes can only be used as a base class for another class. They are often used to create “schemas” which define a set of related classes.
class shape {
public:
    // ...
    virtual draw() = 0;
    // ...
};

class square : public shape {
    public:
    // ...
    virtual draw(); // square class MUST implement this method since it is a pure function in its base class
    // ...
};