15-Object-Oriented Programming

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

15.1. OOP: An Overview

  • Key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding.
    1. Data abstraction: define classes that separate interface from implementation.
    2. Inheritance: define classes that model the relationships among similar types.
    3. Dynamic binding: use objects of these types while ignoring the details of how they differ.

Inheritance

  • Classes related by inheritance form a hierarchy. There is a base class from which the other classes inherit. These inheriting classes are known as derived classes. The base class defines those members that are common to the types in the hierarchy. Each derived class defines those members that are specific to the derived class itself.
  • To model different kinds of pricing strategies, we define a base class named Quote. A Quote object represents undiscounted books. From Quote we inherit Bulk_quote class to represent books that can be sold with a quantity discount.
  • These classes have the following two member functions:
    1. isbn(): return the ISBN. This operation does not depend on the specifics of the inherited class; it will be defined only in class Quote.
    2. net_price(size_t): return the price for purchasing a specified number of copies of a book. This operation is type specific; both Quote and Bulk_quote will define their own version of this function.
  • The base class defines as virtual those functions it expects its derived classes to define for themselves.
class Quote
{
public:
    string isbn() const;
    virtual double net_price(size_t n) const;
};
  • A derived class specify the class(es) from which it inherits by using a class derivation list, which is a colon followed by a comma-separated list of base classes each of which may have an optional access specifier.
class Bulk_quote: public Quote // Bulk_quote inherits from Quote
{
public:
    double net_price(size_t) const override;
};
  • Because Bulk_quote uses public in its derivation list, we can use objects of type Bulk_quote as if they were Quote objects.
  • A derived class must include in its own class body a declaration of all the virtual functions it intends to define for itself and it may or may not include the virtual keyword on these functions. C++11 lets a derived class explicitly note that it intends a member function to override a virtual that it inherits by specifying override after its parameter list.

Dynamic Binding

  • Through dynamic binding, we can use the same code to process objects of either type Quote or Bulk_quote interchangeably. The following function prints the total price for purchasing the given number of copies of a given book:
// calculate and print the price for the given number of copies, applying any discounts
double print_total(ostream &os, const Quote &item, size_t n)
{
    // depending on the type of the object bound to the item parameter
    // calls either Quote::net_price or Bulk_quote::net_price
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() // calls Quote::isbn
      << " # sold: " << n << " total due: " << ret << endl;
    return ret;
}
  • Because the item parameter is a reference to Quote, we can call this function on either a Quote or a Bulk_quote object.
    Because net_price is a virtual function and because print_total calls net_price through a reference, the version of net_price that is run will depend on the type of the object that we pass to print_total:
// basic has type Quote; bulk has type Bulk_quote
print_total(cout, basic, 20);   // calls Quote version of net_price
print_total(cout, bulk, 20);    // calls Bulk_quote version of net_price
  • Dynamic binding(also known as run-time binding) happens when a virtual function is called through a reference(or a pointer) to a base class.

15.2. Defining Base and Derived Classes

15.2.1. Defining a Base Class

class Quote
{
public:
    Quote() = default; // = default see §7.1.4
    Quote(const std::string &book, double sales_price):
        bookNo(book), price(sales_price) {}
    std::string isbn() const
    {
        return bookNo;
    }

    // returns the total sales price for the specified number of items
    // derived classes will override and apply different discount algorithms
    virtual double net_price(std::size_t n) const
    {
        return n * price;
    }
    virtual ~Quote() = default; // dynamic binding for the destructor

private:
    std::string bookNo; // ISBN number of this item

protected:
    double price = 0.0; // normal, undiscounted price
};

Member Functions and Inheritance

  • Base classes should define a virtual destructor even if they do no work.
  • Derived classes inherit the members of their base class. They override the definition for operations that are type dependent by providing its own definition.
  • The base class defines as virtual those functions it expects its derived classes to override. When we call a virtual function through a pointer or reference, the call will be dynamically bound. Depending on the type of the object to which the reference or pointer is bound, the version in the base class or in one of its derived classes will be executed.
    Member functions that are not declared as virtual are resolved at compile time, not run time.
  • Any nonstatic member function(7.6), other than constructor, may be virtual. virtual appears only on the declaration inside the class and may not be used on a function definition outside the class body. A function that is declared as virtual in the base class is implicitly virtual in the derived classes.

Access Control and Inheritance

  • A derived class may access the public members of its base class but may not access the private members. When a base class has members that it wants to let its derived classes use while prohibiting access to those same members by other users, we specify such members after protected access specifier.

Exercises Section 15.2.1

Exercise 15.1

What is a virtual member?

  • A virtual member in a base class expects its derived class define its own version. In particular base classes ordinarily should define a virtual destructor, even if it does no work.

Exercise 15.2

How does the protected access specifier differ from private?

  • private member: base class itself and friend can access.
  • protected members: base class itself, friend and derived classes can access.

Exercise 15.3

Define your own versions of the Quote class and the print_total function.

#include 

using namespace std;

class Quote
{
public:
    Quote() = default;
    Quote(const string &book, double sales_price):
        bookNo(book), price(sales_price) {}
    string isbn() const
    {
        return bookNo;
    }

    virtual double net_price(size_t n) const
    {
        return n * price;
    }
    virtual ~Quote() = default; // dynamic binding for the destructor

private:
    string bookNo; // ISBN number of this item

protected:
    double price = 0.0; // normal, un-discounted price
};

class Bulk_quote: public Quote // Bulk_quote inherits from Quote
{
public:
    double net_price(size_t) const override;
};

double print_total(ostream &os, const Quote &item, size_t n)
{
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() << " # sold: "
       << n << " total due: " << ret << endl;
    return ret;
}

15.2.2. Defining a Derived Class

  • A derived class must specify from which class(es) it inherits in its class derivation list, which is a colon followed by a comma-separated list of names of previously defined classes. Each base class name may be preceded by an optional access specifier, which is one of public, protected, or private.
  • A derived class must declare each inherited member function it intends to override.
class Bulk_quote: public Quote  // Bulk_quote inherits from Quote
{
    Bulk_quote() = default;
    Bulk_quote(const string&, double, size_t, double);
    double net_price(size_t) const override;

private:
    size_t min_qty = 0;         // minimum purchase for the discount to apply
    double discount = 0.0;      // fractional discount to apply
};
  • When the derivation is public, the public members of the base class become part of the interface of the derived class. We can bind an object of a publicly derived type to a pointer or reference to the base type(i.e., we can use a Bulk_quote object where a pointer or reference to Quote is expected).

Virtual Functions in the Derived Class

  • Derived classes can override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then it inherits the version defined in its base class.
  • A derived class may include virtual on the functions it overrides, but not required. C++11 lets a derived class explicitly note that it intends a member function to override a virtual that it inherits by specifying override after the parameter list, or after the const or reference qualifier if the member is a const(7.1.2) or reference (13.6.3) function.

Derived-Class Objects and the Derived-to-Base Conversion

  • A derived object contains multiple parts: a subobject containing the(nonstatic) members defined in the derived class, plus subobjects corresponding to each base class from which the derived class inherits. So, a Bulk_quote object contains four data elements: bookNo and price that inherits from Quote, min_qty and discount that defined by Bulk_quote.
    Figure 15.1. We can think of a Bulk_quote object as consisting of two parts.

Quote item;         // object of base type
Bulk_quote bulk;        // object of derived type
Quote *p = &item;   // p points to a Quote object
p = &bulk;          // p points to the Quote part of bulk
Quote &r = bulk;        // r bound to the Quote part of bulk
  • Derived-to-base conversion: we can bind a base-class reference or pointer to the base-class part of a derived object. Since the compiler applies this conversion implicitly, we can use an object of derived type or a reference to a derived type when a reference to the base type is required or we can use a pointer to a derived type where a pointer to the base type is required.

Derived-Class Constructors

  • A derived class must use a base-class constructor to initialize its base-class part, cannot directly initialize those members. Its constructor uses constructor initializer list to pass arguments to a base-class constructor. Those arguments are used to select which base-class constructor to use to initialize the base-class part of the derived object.
Bulk_quote(const string &book, double p, size_t qty, double disc):
    Quote(book, p), min_qty(qty), discount(disc) {}
  • The base-class part of the object being constructed is initialized first, and then the members of the derived class are initialized in the order in which they are declared in the class. Finally, the function body of the derived-class constructor is run.
  • The constructor body of a derived constructor can assign values to its public or protected base-class members, but it should not do so.

Using Members of the Base Class from the Derived Class

  • A derived class may access the public and protected members of its base class.
// if the specified number of items are purchased, use the discounted price
double Bulk_quote::net_price(size_t cnt) const
{
    if(cnt >= min_qty)
    {
        return cnt * (1 - discount) * price;
    }
    else
    {
        return cnt * price;
    }
}
  • Since the scope of a derived class is nested inside the scope of its base class, there is no distinction between how a member of the derived class uses members defined in its own class(min_qty and discount) and how it uses members defined in its base (price).

Inheritance and static Members

  • If a base class defines static members(7.6), there is a single instance of each static member for the entire hierarchy regardless of the number of classes derived from a base class.
class Base
{
public:
    static void statmem();
};
class Derived: public Base
{
    void f(const Derived&);
};
  • static members obey normal access control, assuming the member is accessible, we can use a static member through either the base or derived:
void Derived::f(const Derived &derived_obj)
{
    Base::statmem();            // ok: Base defines statmem
    Derived::statmem();     // ok: Derived inherits statmem
    // ok: derived objects can be used to access static from base
    derived_obj.statmem();  // accessed through a Derived object
    statmem();              // accessed through this object
}

Declarations of Derived Classes

  • A derived class is declared like any other class(7.3.3). The declaration contains the class name but does not include its derivation list:
class Bulk_quote : public Quote;    // error: derivation list can't appear here
class Bulk_quote;               // ok: right way to declare a derived class
  • The purpose of a declaration is to make known that a name exists and what kind of entity it denotes(class/function/variable).

Classes Used as a Base Class

  • A class must be defined before we can use it as a base class, so it is impossible to derive a class from itself.
class Quote; // declared but not defined
// error: Quote must be defined
class Bulk_quote : public Quote { ... };
  • A base class can itself be a derived class.
class Base { /* ... */ } ;
class D1: public Base { /* ... */ };
class D2: public D1 { /* ... */ };
  • Base is a direct base to D1 and an indirect base to D2. A direct base class is named in the derivation list. An indirect base is one that a derived class inherits through its direct base class.
  • Each class inherits all the members of its direct base class. The most derived class inherits the members of its direct base. The members in the direct base include those it inherits from its base class, and so on up the inheritance chain. Effectively, the most derived object contains a subobject for its direct base and for each of its indirect bases.

Preventing Inheritance

  • Under C++11, we can prevent a class from being used as a base by following the class name with final:
class NoDerived final { /* */ };    // NoDerived can't be a base class
class Base { /* */ };
// Last is final; we cannot inherit from Last
class Last final : Base { /* */ };  // Last can't be a base class
class Bad : NoDerived { /* */ };    // error: NoDerived is final
class Bad2 : Last { /* */ };        // error: Last is final

Exercises Section 15.2.2

Exercise 15.4

Which of the following declarations, if any, are incorrect? Explain why.

class Base { ... };
(a) class Derived : public Derived { ... };
(b) class Derived : private Base { ... };
(c) class Derived : public Base;
  • Wrong: can’t inherited from itself.
  • Wrong: this is definition, not declaration.
  • Wrong: should not include derivation list.

Exercise 15.5

Define your own version of the Bulk_quote class.

  • Same as this section.

Exercise 15.6

Test your print_total function from the exercises in 15.2.1(p. 595) by passing both Quote and Bulk_quote objects to that function.

#include 
#include 

using namespace std;

class Quote
{
public:
    Quote() = default;
    Quote(const string &book, double sales_price):
        bookNo(book), price(sales_price) {}
    string isbn() const
    {
        return bookNo;
    }

    virtual double net_price(size_t n) const
    {
        return n * price;
    }
    virtual ~Quote() = default;

private:
    string bookNo; // ISBN number of this item

protected:
    double price = 0.0; // normal, undiscounted price
};

class Bulk_quote: public Quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const string &book, double p, size_t qty, double disc):
        Quote(book, p), min_qty(qty), discount(disc) {}
    double net_price(size_t) const override;

private:
    size_t min_qty = 0; // minimum purchase for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

// if the specified number of items are purchased, use the discounted price
double Bulk_quote::net_price(size_t cnt) const
{
    if(cnt >= min_qty)
    {
        return cnt * (1 - discount) * price;
    }
    else
    {
        return cnt * price;
    }
}

double print_total(ostream &os, const Quote &item, size_t n)
{
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() << " # sold: "
       << n << " total due: " << ret << endl;
    return ret;
}

int main()
{
    Quote base("hello", 100);
    Bulk_quote derive("world", 100, 5, 0.1);
    print_total(cout, base, 10);
    print_total(cout, derive, 10);

    return 0;
}
/*
Output:
ISBN: hello # sold: 10 total due: 1000
ISBN: world # sold: 10 total due: 900
*/

Exercise 15.7

Define a class that implements a limited discount strategy, which applies a discount to books purchased up to a given limit. If the number of copies exceeds that limit, the normal price applies to those purchased beyond the limit.

  • Omit.

15.2.3. Conversions and Inheritance

  • As built-in pointers, the smart pointer classes(12.1) support the derived-to-base conversion that we can store a pointer to a derived object in a smart pointer to the base type.
  • Since we can bind a reference(or pointer) to a base-class type to a derived object, when we use a reference(or pointer) to a base-class type, we don’t know the actual type of the object to which the pointer or reference is bound.

Static Type and Dynamic Type

  • Distinguish between the static type of a variable or other expression and the dynamic type of the object that expression represents.
    1. The static type of an expression is known at compile time: it is the type with which a variable is declared or that an expression yields.
    2. The dynamic type may not be known until run time: it is the type of the object in memory that the variable or expression represents.
  • The dynamic type of an expression that is neither a reference nor a pointer is the same as that expressions static type. The static type of a pointer or reference to a base class may differ from its dynamic type.

There Is No Implicit Conversion from Base to Derived …

  • The conversion from derived to base exists because every derived object contains a base-class part to which a pointer or reference of the base-class type can be bound. Because a base object might not be part of a derived object, there is no automatic conversion from the base class to its derived class.
Quote base;
Bulk_quote* bulkP = &base;      // error: can't convert base to derived
Bulk_quote& bulkRef = base;     // error: can't convert base to derived
  • We cannot convert from base to derived even when a base pointer or reference is bound to a derived object:
Bulk_quote bulk;
Quote *itemP = &bulk;       // ok: dynamic type is Bulk_quote
Bulk_quote *bulkP = itemP;  // error: can't convert base to derived
  • If the base class has one or more virtual functions, we can use a dynamic_cast(19.2.1) to request a conversion that is checked at run time.
    When we know that the conversion from base to derived is safe, we can use a static_cast(4.11.3) to override the compiler.

…and No Conversion between Objects

  • The automatic derived-to-base conversion applies only for conversions to a reference or pointer type. There is no such conversion from a derived-class type to the base-class type. But it is possible to convert an object of a derived class to its base-class type.
  • When we initialize or assign an object of a class type, we are actually calling a function(constructor and assignment operator, respectively). These members normally have a parameter that is a reference to the const version of the class type.
  • Since these members take references, the derived-to-base conversion lets us pass a derived object to a base-class copy/move operation. These operations are not virtual.
    1. When we pass a derived object to a base-class constructor, the constructor that is run is defined in the base class which knows only about the members of the base class itself.
    2. When we assign a derived object to a base object, the assignment operator that is run is the one defined in the base class which knows only about the members of the base class itself.
  • For example, our bookstore classes use the synthesized versions of copy and assignment(13.1.1 and 13.1.2) that memberwise copy or assign the data members of the class.
Bulk_quote bulk;        // object of derived type
Quote item(bulk);   // uses the Quote::Quote(const Quote&) constructor
item = bulk;            // calls Quote::operator=(const Quote&)
  • When item is constructed, the Quote copy constructor is run that knows only about the bookNo and price members. It copies those members from the Quote part of bulk and ignores the members that are part of the Bulk_quote portion of bulk. Similarly for the assignment of bulk to item. Because the Bulk_quote part is ignored, we say that the Bulk_quote portion of bulk is sliced down.
  • When we initialize or assign an object of a base type from an object of a derived type, only the base-class part of the derived object is copied, moved, or assigned. The derived part of the object is ignored.

Exercises Section 15.2.3

Exercise 15.8

Define static type and dynamic type.

  • The static type of an expression is known at compile time: it is the type with which a variable is declared or that an expression yields.
  • The dynamic type may not be known until run time: it is the type of the object in memory that the variable or expression represents.

Exercise 15.9

When is it possible for an expressions static type to differ from its dynamic type? Give three examples in which the static and dynamic type differ.

  • The static type of a pointer or reference to a base class may differ from its dynamic type.

Exercise 15.10

Recalling the discussion from 8.1(p. 311), explain how the program on page 317 that passed an ifstream to the Sales_data read function works.

  • The function takes a std::istream from which std::ifstream is derived. Hence the ifstream object “is a” istream , which is why it works.

15.3. Virtual Functions

  • Dynamic binding happens when a virtual member function is called through a reference or a pointer to a base-class type(15.1). We must define every virtual function because the compiler has no way to determine whether a virtual function is used.

Key Concept: Conversions among Types Related by Inheritance

  • Three important things about conversions among classes related by inheritance:
    1. The conversion from derived to base applies only to pointer or reference types.
    2. There is no implicit conversion from the base-class type to the derived type.
    3. The derived-to-base conversion may be inaccessible due to access controls(15.5).
  • If classes in an inheritance hierarchy(implicitly or explicitly) define the copy-control members(Chapter 13), we can copy, move, or assign an object of derived type to a base-type object, but copying, moving, or assigning a derived-type object to a base-type object copies, moves, or assigns only the members in the base-class part of the object.

Calls to Virtual Functions May Be Resolved at Run Time

  • When a virtual function is called through a reference or pointer, the compiler calls the function that corresponds to the dynamic type of the object bound to that pointer or reference.
Quote base("0-201-82470-1", 50);
print_total(cout, base, 10);        // calls Quote::net_price
Bulk_quote derived("0-201-82470-1", 50, 5, .19);
print_total(cout, derived, 10);     // calls Bulk_quote::net_price

base = derived;                 // copies the Quote part of derived into base
base.net_price(20);             // calls Quote::net_price
  • Dynamic binding happens only when a virtual function is called through a pointer or a reference. When we call a virtual function on an expression that has a nonreference and nonpointer type, that call is bound at compile time.
    We can change the value(i.e., the contents) of the object that base represents, but there is no way to change the type of that object. The last call is resolved to the Quote version of net_price at compile time.

Key Concept: Polymorphism in C++

  • Polymorphism meaning many forms. Types related by inheritance are polymorphic types because we can use the many forms of these types while ignoring the differences among them. The fact that the static and dynamic types of references and pointers can differ is the cornerstone of how C++ supports polymorphism.
  • When we call a function defined in a base class through a reference or pointer to the base class, we do not know the type of the object on which that member is executed.
    1. Calls to nonvirtual functions are bound at compile time.
    2. Calls to virtual functions are bound at run time.
  • Calls to any function(virtual or not) on an object are bound at compile time to the version defined by the type of the object since the object’s type is fixed.

Virtual Functions in a Derived Class

  • When a derived class overrides a virtual function, it may, but is not required to, repeat virtual because once a function is declared as virtual, it remains virtual in all the derived classes.
  • A derived-class function that overrides an inherited virtual function must have the same parameter types as the base-class function that it overrides.
  • The return type of a virtual in the derived class must match the return type of the function from the base class with one exception that applies to virtuals which return a reference(or pointer) to types that are themselves related by inheritance.
    E.g., if D is derived from B, then a base class virtual can return B* and the derived virtual can return D*. But such return types require that the derived-to-base conversion from D to B is accessible.

The final and override Specifiers

  • It is legal for a derived class to define a function with the same name as a virtual in its base class but with a different parameter list because the compiler considers such a function to be independent from the base-class function. So, the derived version does not override the version in the base class.
    In practice, such declarations often are a mistake: the class author intended to override a virtual from the base but made mistakes in specifying the parameter list.
  • C++11 solution: The compiler will reject a program if a function marked override in a derived class does not override an existing virtual function:
struct B
{
    virtual void f1(int) const;
    virtual void f2();
    void f3();
};

struct D1 : B
{
    void f1(int) const override;    // ok: f1 matches f1 in the base
    void f2(int) override;          // error: B has no f2(int) function
    void f3() override;         // error: f3 not virtual
    void f4() override;         // error: B doesn't have a function named f4
};
  • Since the declarations don’t match, f2 in D1 it is a new function that happens to have the same name as f2 in B. Because we intended this declaration to be an override and it isn’t, the compiler will generate an error.
    Only a virtual function can be overridden, the compiler will reject f3 in D1.
  • We can designate a function as final. Any attempt to override a function that has been defined as final will be flagged as an error.
struct D2 : B
{
    // inherits f2() and f3() from B and overrides f1(int)
    void f1(int) const final; // subsequent classes can't override f1(int)
};
struct D3 : D2
{
    void f2();          // ok: overrides f2 inherited from the indirect base, B
    void f1(int) const; // error: D2 declared f2 as final
};
  • final and override specifiers appear after the parameter list(including any const or reference qualifiers) and after a trailing return(6.3.3).

Virtual Functions and Default Arguments

  • A virtual function can have default arguments(6.5.1). If a call uses a default argument, the value that is used is the one defined by the static type through which the function is called.
  • When a call is made through a reference or pointer to base, the default argument(s) will be those defined in the base class. The base-class arguments will be used even when the derived version of the function is run. In this case, the derived function will be passed the default arguments defined for the base-class version of the function. If the derived function relies on being passed different arguments, the program will not execute as expected.
  • Virtual functions that have default arguments should use the same argument values in the base and derived classes.

Circumventing the Virtual Mechanism

  • We can force the call to use a particular version of that virtual by using the scope operator. Such calls are resolved at compile time. Ordinarily, only code inside member functions(or friends) should need to use the scope operator to circumvent the virtual mechanism.
// calls the version from the base class regardless of the dynamic type of baseP
double undiscounted = baseP->Quote::net_price(42);
  • If a derived virtual function that intended to call its base-class version without the scope operator, the call will be resolved at run time as a call to the derived version itself, resulting in an infinite recursion.

Exercises Section 15.3

Exercise 15.11

Add a virtual debug function to your Quote class hierarchy that displays the data members of the respective classes.

#include 
#include 

using namespace std;

class Quote
{
public:
    Quote(const string &book, double sales_price):
        bookNo(book), price(sales_price) {}

    virtual ~Quote() = default;
    virtual void Debug() const
    {
        cout << "bookNo = " << bookNo << ", price = " << price << '\n';
    }

protected:
    string bookNo; // ISBN number of this item
    double price = 0.0; // normal, undiscounted price
};

class Bulk_quote: public Quote
{
public:
    Bulk_quote(const string &book, double p, size_t qty, double disc):
        Quote(book, p), min_qty(qty), discount(disc) {}
    void Debug() const override
    {
        cout << "bookNo = " << bookNo << ", price = " << price
             << ", min_qty = " << min_qty << ", discount = " << discount << '\n';
    }

private:
    size_t min_qty = 0; // minimum purchase for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

int main()
{
    Quote base("hello", 100);
    Bulk_quote derive("world", 100, 5, 0.1);
    base.Debug();
    derive.Debug();

    return 0;
}
/*
Output:
bookNo = hello, price = 100
bookNo = world, price = 100, min_qty = 5, discount = 0.1
*/

Exercise 15.12

Is it ever useful to declare a member function as both override and final? Why or why not?

  • Yes because they have different functions.
    1. override: overriding the same name virtual function in base class.
    2. final: preventing any overriding this virtual function by any derived classes that are more lower at the hierarchy.

Exercise 15.13

Given the following classes, explain each print function:

class base
{
public:
    string name()
    {
        return base_name;
    }
    virtual void print(ostream &os)
    {
        os << base_name;
    }

private:
    string base_name;
};

class derived : public base
{
public:
    void print(ostream &os)
    {
        print(os);
        os << " " << i;
    }

private:
    int i;
};

If there is a problem in this code, how would you fix it?

  • Segmentation fault: print(os) in derived will call itself indefinitely.
  • Fix: base::print(os);

Exercise 15.14

Given the classes from the previous exercise and the following objects, determine which function is called at run time:

base bobj;
base *bp1 = &bobj;
base &br1 = bobj;
derived dobj;
base *bp2 = &dobj;
base &br2 = dobj;
(a) bobj.print();
(b) dobj.print();
(c) bp1->name();
(d) bp2->name();
(e) br1.print();
(f) br2.print();
  • base; derived; base; base; base; derived
  • Test program:
#include 
#include 

using namespace std;

class base
{
public:
    void name()
    {
        cout << basename << '\n';
    }
    virtual void print()
    {
        cout << "base\n";
    }

private:
    string basename = "default";
};

class derived : public base
{
public:
    void print()
    {
        cout << "derived\n";
    }

private:
    int i = 7188;
};

int main()
{
    base bobj;
    base *bp1 = &bobj;
    base &br1 = bobj;
    derived dobj;
    base *bp2 = &dobj;
    base &br2 = dobj;

    bobj.print();
    dobj.print();
    bp1->name();
    bp2->name();
    br1.print();
    br2.print();

    return 0;
}
/*
Output:
base
derived
default
default
base
derived
*/

15.4. Abstract Base Classes

Pure Virtual Functions

  • A pure virtual function has = 0 in place of a function body(i.e., just before the semicolon that ends the declaration). = 0 may appear only on the declaration of a virtual function in the class body.
    We can provide a definition for a pure virtual only outside the class.
// class to hold the discount rate and quantity
// derived classes will implement pricing strategies using these data
class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    Disc_quote(const string& book, double price, size_t qty, double disc):
        Quote(book, price), quantity(qty), discount(disc) {}
    double net_price(size_t) const = 0;

protected:
    size_t quantity = 0; // purchase size for the discount to apply
    double discount = 0.0; // fractional discount to apply
};
  • We cannot define objects of abstract class type directly, but constructors in classes derived from Disc_quote will use the Disc_quote constructors to construct the Disc_quote part of their objects.

Classes with Pure Virtuals Are Abstract Base Classes

  • A class containing(or inheriting without overridding) a pure virtual function is an abstract base class that defines an interface for subsequent classes to override. We cannot directly create objects of abstract base class. We can define objects of classes that inherit from them, so long as those classes override the pure virtual functions.
// Disc_quote declares pure virtual functions, which Bulk_quote will override
Disc_quote discounted;  // error: can't define a Disc_quote object
Bulk_quote bulk;            // ok: Bulk_quote has no pure virtual functions

A Derived Class Constructor Initializes Its Direct Base Class Only

// the discount kicks in when a specified number of copies of the same book are sold
// the discount is expressed as a fraction to use to reduce the normal price
class Bulk_quote: public Disc_quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const string& book, double price, size_t qty, double disc):
        Disc_quote(book, price, qty, disc) {}
    // overrides the base version to implement the bulk purchase discount policy
    double net_price(size_t) const override;
};
  • Bulk_quote has a direct base class, Disc_quote, and an indirect base class, Quote. Each Bulk_quote object has three subobjects: an empty Bulk_quote part, a Disc_quote subobject, and a Quote subobject.
  • Each class controls the initialization of objects of its type. Our Bulk_quote constructor passes its arguments to the Disc_quote constructor. That constructor in turn runs the Quote constructor. The Quote constructor initializes the bookNo and price members of bulk. When the Quote constructor ends, the Disc_quote constructor runs and initializes the quantity and discount members. At this point, the Bulk_quote constructor resumes. That constructor has no further initializations or any other work to do.

Key Concept: Refactoring

  • Adding Disc_quote to the Quote hierarchy is an example of refactor. Refactoring involves redesigning a class hierarchy to move operations and/or data from one class to another. Refactoring is common in object-oriented applications.

Exercises Section 15.4

Exercise 15.15

Define your own versions of Disc_quote and Bulk_quote.

// class to hold the discount rate and quantity
// derived classes will implement pricing strategies using these data
class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    Disc_quote(const string& book, double price, size_t qty, double disc):
        Quote(book, price), quantity(qty), discount(disc) {}
    double net_price(size_t) const = 0;

protected:
    size_t quantity = 0; // purchase size for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

// the discount kicks in when a specified number of copies of the same book are sold
// the discount is expressed as a fraction to use to reduce the normal price
class Bulk_quote: public Disc_quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const string& book, double price, size_t qty, double disc):
        Disc_quote(book, price, qty, disc) {}
    // overrides the base version to implement the bulk purchase discount policy
    double net_price(size_t) const override
    {
        return 0.0;
    }
};

Exercise 15.16

Rewrite the class representing a limited discount strategy, which you wrote for the exercises in 15.2.2(p. 601), to inherit from Disc_quote.

// if the specified number of items are purchased, use the discounted price
double Bulk_quote::net_price(size_t cnt) const
{
    if(cnt >= quantity)
    {
        return cnt * (1 - discount) * price;
    }
    else
    {
        return cnt * price;
    }
}

Exercise 15.17

Try to define an object of type Disc_quote and see what errors you get from the compiler.

  • error: cannot declare variable ‘obj’ to be of abstract type ‘Disc_quote’

15.5. Access Control and Inheritance

protected Members

  • A class uses protected for members that it is willing to share with its derived classes but wants to protect from general access.
    1. As private: protected members are inaccessible to users of the class.
    2. As public: protected members are accessible to members and friends of classes derived from this class.
  • A derived class member or friend may access the protected members of the base class only through a derived object. The derived class has no special access to the protected members of base-class objects.
class Base
{
protected:
    int prot_mem; // protected member
};
class Sneaky : public Base
{
    friend void clobber(Sneaky&);   // can access Sneaky::prot_mem
    friend void clobber(Base&);     // can't access Base::prot_mem
    int j;                          // j is private by default
};
// ok: clobber can access the private and protected members in Sneaky objects
void clobber(Sneaky &s)
{
    s.j = s.prot_mem = 0;
}
// error: clobber can't access the protected members in Base
void clobber(Base &b)
{
    b.prot_mem = 0;
}
  • Members and friends of a derived class can access the protected members only in base-class objects that are embedded inside a derived type object; they have no special access to ordinary objects of the base type.

public, private, and protected Inheritance

class Base
{
public:
    void pub_mem(); // public member
protected:
    int prot_mem; // protected member
private:
    char priv_mem; // private member
};
struct Pub_Derv : public Base
{
    // ok: derived classes can access protected members
    int f()
    {
        return prot_mem;
    }
    // error: private members are inaccessible to derived classes
    char g()
    {
        return priv_mem;
    }
};
struct Priv_Derv : private Base
{
    // private derivation doesn't affect access in the derived class
    int f1() const
    {
        return prot_mem;
    }
};
  • Access to the members of a direct base class is only controlled by the access specifiers in the base class itself, irrelevant with the derivation access specifier.
  • The derivation access specifier control the access owned by users of the derived class (including other classes derived from the derived class) to the members inherited from Base. When the inheritance is public, members retain their access specification.
Pub_Derv d1;    // members inherited from Base are public
Priv_Derv d2;   // members inherited from Base are private
d1.pub_mem();   // ok: pub_mem is public in the derived class
d2.pub_mem();   // error: pub_mem is private in the derived class
struct Derived_from_Public : public Pub_Derv
{
    // ok: Base::prot_mem remains protected in Pub_Derv
    int use_base()
    {
        return prot_mem;
    }
};
struct Derived_from_Private : public Priv_Derv
{
    // error: all the members that Priv_Derv inherited from Base are private.
    int use_base()
    {
        return prot_mem;
    }
};
  • If Prot_Derv class use protected inheritance, the public members of Base would be protected members in that class. Users of Prot_Derv would have no access to pub_mem, but the members and friends of Prot_Derv could access that inherited member.

Accessibility of Derived-to-Base Conversion

  • Whether the derived-to-base conversion(15.2.2) is accessible depends on which code is trying to use the conversion and the access specifier used in the derived class derivation.
  • Assuming D inherits from B:
    1. User can use the derived-to-base conversion only if D inherits publicly from B.
    2. Member functions and friends of D can use the conversion to B regardless of how D inherits from B. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class.
    3. Member functions and friends of classes derived from D may use the derived-to-base conversion if D inherits from B using either public or protected.
  • Summary: For any conditions, if a public member of the base class would be accessible, then the derived-to-base conversion is also accessible, and not otherwise.

Key Concept: Class Design and protected Members

  • A base class should make its interface members public.
  • For implementation member, it should be protected if it provides an operation or data that a derived class will need to use in its own implementation; otherwise should be private.

Friendship and Inheritance

  • As friendship is not transitive(7.3.4), friendship is also not inherited.
    1. Friends of the base have no special access to members of its derived classes.
    2. Friends of the derived have no special access to the base class.
class Base
{
    // added friend declaration; other members as before
    friend class Pal;           // Pal has no access to classes derived from Base
};
class Pal
{
public:
    int f(Base b)
    {
        return b.prot_mem;  // ok: Pal is a friend of Base
    }
    int f2(Sneaky s)
    {
        return s.j;         // error: Pal not friend of Sneaky
    }
    // access to a base class is controlled by the base class, even inside a derived object
    int f3(Sneaky s)
    {
        return s.prot_mem;  // ok: Pal is a friend
    }
};
  • f3 is legal because each class controls access to its own members. Pal is a friend of Base, so Pal can access the members of Base objects including the Base objects that are embedded in an object of a type derived from Base.
  • When a class makes another class a friend, it is only that class to which friendship is granted. The base classes of, and classes derived from, the friend have no special access to the befriending class.
// D2 has no access to protected or private members in Base
class D2 : public Pal
{
public:
    int mem(Base b)
    {
        return b.prot_mem; // error: friendship doesn't inherit
    }
};

Exempting Individual Members

  • Sometimes we need to change the access level of a name that a derived class inherits. We can do so by providing a using declaration(3.1, p. 82):
class Base
{
public:
    size_t size() const
    {
        return n;
    }
protected:
    size_t n;
};
class Derived : private Base // note: private inheritance
{
public:
    // maintain access levels for members related to the size of the object
    using Base::size;
protected:
    using Base::n;
};
  • Since Derived uses private inheritance, the inherited members(size and n) are private members of Derived by default. The using declarations change the accessibility of these members: users of Derived can access the size member, and classes derived from Derived can access n.
  • A using declaration inside a class can only name accessible(e.g., not private) member of a direct or indirect base class. If a using declaration follows:
    1. public: accessible to all users of the class.
    2. protected: accessible to the members, friends, and derived classes.
    3. private: accessible to members and friends only.

Default Inheritance Protection Levels

  • By default, a derived class defined with class has private inheritance; with struct has public inheritance:
class Base      { /* ... */ };
struct D1: Base { /* ... */ }; // public inheritance by default
class D2: Base  { /* ... */ }; // private inheritance by default
  • The only difference between classes defined by struct and class is the default access specifier for members and the default derivation access specifier.

Exercises Section 15.5

Exercise 15.18

Given the classes from page 612 and page 613, and assuming each object has the type specified in the comments, determine which of these assignments are legal. Explain why those that are illegal aren’t allowed:

Base *p = &d1;  // d1 has type Pub_Derv
p = &d2;        // d2 has type Priv_Derv
p = &d3;        // d3 has type Prot_Derv
p = &dd1;       // dd1 has type Derived_from_Public
p = &dd2;       // dd2 has type Derived_from_Private
p = &dd3;       // dd3 has type Derived_from_Protected
  • Only d1 and dd1 is legal.
    User code may use the derived-to-base conversion only if D inherits publicly from B.
#include 

using namespace std;

class Base
{
public:
    int pub_mem;
protected:
    int prot_mem;
private:
    char priv_mem;
};

struct Pub_Derv: public Base {};
struct Priv_Derv: private Base {};
struct Prot_Derv: protected Base {};

struct Derived_from_Public: public Pub_Derv {};
struct Derived_from_Private: public Priv_Derv {};
struct Derived_from_Protected: public Prot_Derv {};

int main()
{
    Pub_Derv d1;
    Priv_Derv d2;
    Prot_Derv d3;
    Derived_from_Public dd1;
    Derived_from_Private dd2;
    Derived_from_Protected dd3;

    Base *p = &d1; // d1 has type Pub_Derv
//  p = &d2; // d2 has type Priv_Derv
//  p = &d3; // d3 has type Prot_Derv
    p = &dd1; // dd1 has type Derived_from_Public
//  p = &dd2; // dd2 has type Derived_from_Private
//  p = &dd3; // dd3 has type Derived_from_Protected

    return 0;
}

Exercise 15.19

Assume that each of the classes from page 612 and page 613 has a member function of the form:

void memfcn(Base &b) { b = *this; }

For each class, determine whether this function would be legal.

  • Member functions and friends of D can use the conversion to B regardless of how D inherits from B. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class. Hence, the 3 below are all legal: Pub_Derv & Priv_Derv & Prot_Derv.
  • Member functions and friends of classes derived from D may use the derived-to-base conversion if D inherits from B using either public or protected. Hence: Derived_from_Public & Derived_from_Protected is legal; Derived_from_Private illegal.
#include 

using namespace std;

class Base
{
public:
    int pub_mem;
protected:
    int prot_mem;
private:
    char priv_mem;
};

struct Pub_Derv: public Base
{
    void memfcn(Base &b)
    {
        b = *this;
    }
};
struct Prot_Derv: protected Base
{
    void memfcn(Base &b)
    {
        b = *this;
    }
};
struct Priv_Derv: private Base
{
    void memfcn(Base &b)
    {
        b = *this;
    }
};

struct Derived_from_Public: public Pub_Derv
{
    void memfcn(Base &b)
    {
        b = *this;
    }
};
struct Derived_from_Protected: public Prot_Derv
{
    void memfcn(Base &b)
    {
        b = *this;
    }
};
struct Derived_from_Private: public Priv_Derv
{
    /*void memfcn(Base &b)
    {
        b = *this;
    }*/
};

int main()
{
    Pub_Derv d1;
    Priv_Derv d2;
    Prot_Derv d3;
    Derived_from_Public dd1;
    Derived_from_Private dd2;
    Derived_from_Protected dd3;

    return 0;
}

Exercise 15.20

Write code to test your answers to the previous two exercises.

  • Already in the previous two exercises.

Exercise 15.21

Choose one of the following general abstractions containing a family of types(or choose one of your own). Organize the types into an inheritance hierarchy:
(a) Graphical file formats(such as gif, tiff, jpeg, bmp)
(b) Geometric primitives(such as box, circle, sphere, cone)
(c) C++ language types(such as class, function, member function)

  • Omit.

Exercise 15.22

For the class you chose in the previous exercise, identify some of the likely virtual functions as well as public and protected members.

  • Omit.

15.6. Class Scope under Inheritance

  • Each class defines its own scope(7.4) within which its members are defined. Under inheritance, the scope of a derived class is nested(2.2.4) inside the scope of its base classes. If a name is unresolved within the scope of the derived class, the enclosing base-class scopes are searched for a definition of that name.

Name Lookup Happens at Compile Time

  • The static type(15.2.3) of an object, reference, or pointer determines which members of that object are visible and can be used even if the static and dynamic types differ (when a reference or pointer to a base class is used). For example, we add a member to the Disc_quote class that returns a pair(11.2.3) holding the minimum(or maximum) quantity and the discounted price:
class Disc_quote : public Quote
{
public:
    pairdouble> discount_policy() const
    {
        return {quantity, discount};
    }
    // other members as before
};
  • We can use discount_policy only through an object, pointer, or reference of type Disc_quote or of a class derived from Disc_quote.
class Bulk_quote: public Disc_quote {...};
Bulk_quote bulk;
Bulk_quote *bulkP = &bulk;  // static and dynamic types are the same
Quote *itemP = &bulk;       // static and dynamic types differ
bulkP->discount_policy();   // ok: bulkP has type Bulk_quote*
itemP->discount_policy();   // error: itemP has type Quote*
  • Though bulk has member discount_policy(), that member is not visible through itemP because it is a pointer to Quote, which means that the search for discount_policy starts in class Quote. The Quote class has no member named discount_policy, so we cannot call that member on an object, reference, or pointer of type Quote.

Name Collisions and Inheritance

  • A derived class can reuse a name defined in one of its direct or indirect base classes. Names defined in an inner scope(e.g., a derived class) hide uses of that name in the outer scope(e.g., a base class)(2.2.4).
struct Base
{
    Base(): mem(0) {}
protected:
    int mem;
};
struct Derived : Base
{
    Derived(int i): mem(i) {}   // initializes Derived::mem to i
    // Base::mem is default initialized
    int get_mem()
    {
        return mem;         // returns Derived::mem
    }
protected:
    int mem;                // hides mem in the base
};

Using the Scope Operator to Use Hidden Members

  • We can use a hidden base-class member by using the scope operator.
struct Derived : Base
{
    int get_base_mem()
    {
        return Base::mem;
    }
    // ...
};

Key Concept: Name Lookup and Inheritance

p->mem(); or
obj.mem();
  • 1.Determine the static type of p(or obj). Because we are calling a member, that type must be a class type.
    2.Look for mem in the class that corresponds to the static type of p(or obj). If mem is not found, look in the direct base class and continue up the chain of classes until mem is found or the last class is searched. If mem is not found in the class or its enclosing base classes, then the call will compile error.
    3.Once mem is found, do type checking(6.1) to see if this call is legal given the definition that was found.
    4.If the call is legal, the compiler generates code, which depending on whether the call is virtual:
    -1- If mem is virtual and the call is made through a reference or pointer, then the compiler generates code to determine at run time which version to run based on the dynamic type of the object.
    -2- If the function is nonvirtual, or if the call is on an object(not a reference or pointer), the compiler generates a normal function call.

As Usual, Name Lookup Happens before Type Checking

  • Because functions declared in an inner scope do not overload functions declared in an outer scope(6.4.1), so functions defined in a derived class do not overload members defined in its base class(es). If a member in a derived class(i.e., in an inner scope) has the same name as a base-class member(i.e., a name defined in an outer scope), then the derived member hides the base-class member within the scope of the derived class. The base member is hidden even if the functions have different parameter lists.
struct Base
{
    int memfcn();
};
struct Derived : Base
{
    int memfcn(int);    // hides memfcn in the base
};
Derived d;
Base b;
b.memfcn();         // calls Base::memfcn
d.memfcn(10);       // calls Derived::memfcn
d.memfcn();         // error: memfcn with no arguments is hidden
d.Base::memfcn();   // ok: calls Base::memfcn
  • For the 3rd call, the compiler looks for the name memfcn in Derived. That class defines a member named memfcn and the search stops. The version of memfcn in Derived expects an int argument. This call provides no such argument; it is in error.

Virtual Functions and Scope

  • Virtual functions must have the same parameter list in the base and derived classes (15.3). If the base and derived members took arguments that differed from one another, there would be no way to call the derived version through a reference or pointer to the base class.
class Base
{
public:
    virtual int fcn();
};
class D1 : public Base
{
public:
    // hides fcn in the base; this fcn is not virtual
    // D1 inherits the definition of Base::fcn()
    int fcn(int);       // parameter list differs from fcn in Base
    virtual void f2();  // new virtual function that does not exist in Base
};
class D2 : public D1
{
public:
    int fcn(int);   // nonvirtual function hides D1::fcn(int)
    int fcn();  // overrides virtual fcn from Base
    void f2();  // overrides virtual f2 from D1
};
  • The fcn function in D1 does not override the virtual fcn from Base because they have different parameter lists. Instead, it hides fcn from the base. D1 has two functions named fcn: D1 inherits a virtual named fcn from Base and defines its own, nonvirtual member named fcn that takes an int parameter.

Calling a Hidden Virtual through the Base Class

Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // virtual call, will call Base::fcn at run time
bp2->fcn(); // virtual call, will call Base::fcn at run time
bp3->fcn(); // virtual call, will call D2::fcn at run time

D1 *d1p = &d1obj;
D2 *d2p = &d2obj;
bp2->f2(); // error: Base has no member named f2
d1p->f2(); // virtual call, will call D1::f2() at run time
d2p->f2(); // virtual call, will call D2::f2() at run time
  • The first three calls are all made through pointers to the base class. Because fcn is virtual, the compiler generates code to decide at run time which version to call. For bp2, the underlying object is a D1 that did not override the fcn function which takes no arguments. So, the call through bp2 is resolved at run time to the version defined in Base.
Base *p1 = &d2obj;
D1 *p2 = &d2obj;
D2 *p3 = &d2obj;
p1->fcn(42); // error: Base has no version of fcn that takes an int
p2->fcn(42); // statically bound, calls D1::fcn(int)
p3->fcn(42); // statically bound, calls D2::fcn(int)
  • The dynamic type doesn’t matter when we call a nonvirtual function. The version that is called depends only on the static type of the pointer.

Overriding Overloaded Functions

  • A member function can be overloaded. A derived class can override zero or more instances of the overloaded functions it inherits. If a derived class wants to make all the overloaded versions available through its type, then it must override all of them or none of them.
  • A derived class can provide a using declaration(15.5) for the overloaded member. A using declaration specifies only a name; it may not specify a parameter list. A using declaration for a base-class member function adds all the overloaded instances of that function to the scope of the derived class. Having brought all the names into its scope, the derived class needs to define only those functions that truly depend on its type. It can use the inherited definitions for the others.
  • The normal rules for a using declaration inside a class apply to names of overloaded functions(15.5); every overloaded instance of the function in the base class must be accessible to the derived class. The access to the overloaded versions that are not otherwise redefined by the derived class will be the access in effect at the point of the using declaration.

Exercises Section 15.6

Exercise 15.23

Assuming class D1 on page 620 had intended to override its inherited fcn function, how would you fix that class? Assuming you fixed the class so that fcn matched the definition in Base, how would the calls in that section be resolved?

  • Remove parameter list int.
#include 

using namespace std;

class Base
{
public:
    virtual int fcn()
    {
        cout << "Base::fcn()\n";
        return 0;
    }
};

class D1 : public Base
{
public:
    int fcn() override
    {
        cout << "D1::fcn()\n";
        return 0;
    }
    virtual void f2()
    {
        cout << "D1::f2()\n";
    }
};

class D2 : public D1
{
public:
    int fcn(int)
    {
        cout << "D2::fcn(int)\n";
    }
    int fcn() override
    {
        cout << "D2::fcn()\n";
        return 0;
    }
    void f2() override
    {
        cout << "D2::f2()\n";
    }
};

int main()
{
    Base bobj;
    D1 d1obj;
    D2 d2obj;

    Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
    bp1->fcn(); // virtual call, will call Base::fcn at run time
    bp2->fcn(); // virtual call, will call D1::fcn at run time
    bp3->fcn(); // virtual call, will call D2::fcn at run time

    D1 *d1p = &d1obj;
    D2 *d2p = &d2obj;
    //bp2->f2(); // error: ‘class Base’ has no member named ‘f2’
    d1p->f2(); // virtual call, will call D1::f2() at run time
    d2p->f2(); // virtual call, will call D2::f2() at run time

    return 0;
}
/*
Output:
Base::fcn()
D1::fcn()
D2::fcn()
D1::f2()
D2::f2()
*/

15.7. Constructors and Copy Control

  • A class in an inheritance hierarchy controls what happens when objects of its type are created, copied, moved, assigned, or destroyed. If a class(base or derived) does not define one of the copy-control operations itself, the compiler will synthesize that operation.

15.7.1. Virtual Destructors

  • The destructor is run when we delete a pointer to a dynamically allocated object (13.1.3). It is possible that the static type of the pointer differs from the dynamic type of the object being destroyed(15.2.2). We arrange to run the proper destructor by defining the destructor as virtual in the base class.
class Quote
{
public:
    // virtual destructor needed if a base pointer pointing to a derived object is deleted
    virtual ~Quote() = default; // dynamic binding for the destructor
};
  • Classes derived from Quote have virtual destructors, whether they use the synthesized destructor or define their own version. So long as the base class destructor is virtual, when we delete a pointer to base, the correct destructor will be run:
Quote *itemP = new Quote;   // same static and dynamic type
delete itemP;                   // destructor for Quote called
itemP = new Bulk_quote;     // static and dynamic types differ
delete itemP;                   // destructor for Bulk_quote called
  • Executing delete on a pointer to base that points to a derived object has undefined behavior if the bases destructor is not virtual.
  • Destructors for base classes are an exception to the rule: if a class needs a destructor, it also needs copy and assignment(13.1.4). If a base class has an empty destructor in order to make it virtual, it doesn’t indicate that the assignment operator or copy constructor is also needed.

Virtual Destructors Turn Off Synthesized Move

  • If a class defines a destructor(even if it uses = default to use the synthesized version), the compiler will not synthesize a move operation for that class(13.6.2).

Exercises Section 15.7.1

Exercise 15.24

What kinds of classes need a virtual destructor? What operations must a virtual destructor perform?

  • Base class.
  • Release resources that dynamically allocated.

15.7.2. Synthesized Copy Control and Inheritance

    1. The synthesized copy-control members in a base or a derived class memberwise initialize, assign, or destroy the members of the class itself.
    2. These synthesized members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class(synthesized or user-provided definition). The corresponding member should be accessible(15.5) and is not a deleted function.
  • In addition to destroying its own members, the destruction phase of a destructor in a derived class destroys its direct base. That destructor in turn invokes the destructor for its own direct base, if any, so on up to the root of the hierarchy.

Base Classes and Deleted Copy Control in the Derived

  • The synthesized copy-control members of either a base or a derived class may be defined as deleted for the same reasons as in any other class(13.1.6 and 13.6.2).
  • The way in which a base class is defined can cause a derived-class member to be defined as deleted:
    1. If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible(15.5), then the corresponding member in the derived class is defined as deleted, because the compiler can’t use the base-class member to construct, assign, or destroy the base-class part of the object.
    2. If the base class has an inaccessible or deleted destructor, then the synthesized default and copy constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object.
    3. The compiler will not synthesize a deleted move operation. If we use = default to request a move operation, it will be a deleted function in the derived if the corresponding operation in the base is deleted or inaccessible, because the base class part cannot be moved. The move constructor will also be deleted if the base class destructor is deleted or inaccessible.
class B
{
public:
    B();
    B(const B&) = delete;
    // other members, not including a move constructor
};
class D : public B
{
    // no constructors
};
D d;            // ok: D's synthesized default constructor uses B's default constructor
D d2(d);            // error: D's synthesized copy constructor is deleted
D d3(move(d));  // error: implicitly uses D's deleted copy constructor
  • The base class, B, has an accessible default constructor and an explicitly deleted copy constructor. Because the copy constructor is defined, the compiler will not synthesize a move constructor for class B(13.6.2). So, we can neither move nor copy objects of type B.
  • If a class derived from B wanted to allow its objects to be copied or moved, that derived class would have to define its own versions of these constructors. That class would have to decide how to copy or move the members in it base-class part. In practice, if a base class does not have a default, copy, or move constructor, then its derived classes usually don’t either.

Move Operations and Inheritance

  • If base classes define a virtual destructor, they don’t get synthesized move operations by default. Classes derived from a base class that doesn’t have move operations don’t get synthesized move operations by default.
class Quote
{
public:
    Quote() = default;                          // memberwise default initialize
    Quote(const Quote&) = default;              // memberwise copy
    Quote(Quote&&) = default;                   // memberwise copy
    Quote& operator=(const Quote&) = default;   // copy assign
    Quote& operator=(Quote&&) = default;        // move assign
    virtual ~Quote() = default;
    // other members as before
};
  • Quote objects will be memberwise copied, moved, assigned, and destroyed. Classes derived from Quote will automatically obtain synthesized move operations unless they have members that otherwise preclude move.

Exercises Section 15.7.2

Exercise 15.25

Why did we define a default constructor for Disc_quote? What effect, if any, would removing that constructor have on the behavior of Bulk_quote?

  • Without it, the default Disc_quote constructor will be defined as deleted. So, Bulk_quote default constructor is also deleted. The following statement will be error:
    Bulk_quote obj; // error: use of deleted function ‘Bulk_quote::Bulk_quote()’

15.7.3. Derived-Class Copy-Control Members

  • When a derived class defines a copy or move operation(constructor/copy constructor/ move constructor/assignment operator), that operation is responsible for copying or moving the entire object, including base-class members.
  • On the other hand, the destructor is responsible only for destroying the resources allocated by the derived class. The members of an object are implicitly destroyed (13.1.3) and the base-class part of a derived object is destroyed automatically.

Defining a Derived Copy or Move Constructor

  • When we define a copy or move constructor(13.1.1 and 13.6.2) for a derived class, we ordinarily use the corresponding base-class constructor to initialize the base part of the object.
class Base
{
    /* ... */
};
class D: public Base
{
public:
    // by default, the base class default constructor initializes the base part of an object
    // to use the copy or move constructor, we must explicitly call that
    // constructor in the constructor initializer list
    D(const D& d): Base(d) // copy the base members
    /* initializers for members of D */
    {
        /* ... */
    }
    D(D&& d): Base(std::move(d)) // move the base members
    /* initializers for members of D */
    {
        /* ... */
    }
};
  • The initializer Base(d) passes a D object to a base-class constructor. Base(d) ordinarily match the Base copy constructor and d is bound to the Base& parameter. The Base copy constructor will copy the base part of d into the object that is being created.
// probably incorrect definition of the D copy constructor
// base-class part is default initialized, not copied
D(const D& d) // member initializers, but no base-class initializer
{
    /* ... */
}
  • If the initializer for the base class omits, the Base default constructor would be used to initialize the base part of a D object. Assuming Ds constructor copies the derived members from d, this newly constructed object would be oddly configured: its Base members would hold default values, while its D members would be copies of the data from another object.
  • By default, the base-class default constructor initializes the base-class part of a derived object. If we want copy/move the base-class part, we must explicitly use the copy/move constructor for the base class in the derived constructor initializer list.

Derived-Class Assignment Operator

  • A derived-class assignment operator(13.1.2 and 13.6.2) must assign its base part explicitly.
// Base::operator=(const Base&) is not invoked automatically
D &D::operator=(const D &rhs)
{
    Base::operator=(rhs); // assigns the base part
    // assign the members in the derived class, as usual,
    // handling self-assignment and freeing existing resources as appropriate
    return *this;
}
  • Assignment operator starts by calling the base-class assignment operator to assign the members of the base part of the derived object. The base-class operator should correctly handle self-assignment and free the old value in the base part of the left-hand operand and assign the new values from rhs. After base-class part finishes, we continue doing whatever is needed to assign the members in the derived class.
  • A derived constructor or assignment operator can use its corresponding base class operation regardless of whether the base defined its own version of that operator or uses the synthesized version.

Derived-Class Destructor

  • The data members of an object are implicitly destroyed after the destructor body completes(13.1.3); the base-class parts of an object are also implicitly destroyed. So, a derived destructor is responsible only for destroying the resources allocated by the derived class:
class D: public Base
{
public:
    // Base::~Base invoked automatically
    ~D()
    {
        /* do what it takes to clean up derived members */
    }
};
  • Objects are destroyed in the opposite order from which they are constructed: the derived destructor is run first, and then the base-class destructors are invoked, back up through the inheritance hierarchy.

Calls to Virtuals in Constructors and Destructors

  • While base-class constructors and destructors are executing, the object is incomplete:
    1. Since the base-class part of a derived object is constructed first, when the base-class constructor is executing, the derived part of the object is uninitialized.
    2. Since derived objects are destroyed in reverse order, when a base class destructor runs, the derived part has already been destroyed.
  • While an object is being constructed it is treated as if it has the same class as the constructor; calls to virtual functions will be bound as if the object has the same type as the constructor itself; similarly for destructors.
    This binding applies to virtuals called directly or indirectly from a function that the constructor/destructor calls.
  • What happens if the derived-class version of a virtual was called from a base-class constructor?
    The virtual may access members of the derived object that are uninitialized while a base constructor is running. If such access were allowed, the program may crash.

Exercises Section 15.7.3

Exercise 15.26

Define the Quote and Bulk_quote copy-control members to do the same job as the synthesized versions. Give them and the other constructors print statements that identify which function is running. Write programs using these classes and predict what objects will be created and destroyed. Compare your predictions with the output and continue experimenting until your predictions are reliably correct.

  • Omit.

15.7.4. Inherited Constructors

  • Under C++11, a derived class can reuse the constructors defined by its direct base class. As a class may initialize only its direct base class, a class may inherit constructors only from its direct base. A class cannot inherit the default, copy, and move constructors. If the derived class does not directly define these constructors, the compiler synthesizes them as usual.
  • A derived class inherits its base-class constructors by providing a using declaration that names its direct base class.
class Bulk_quote: public Disc_quote
{
public:
    using Disc_quote::Disc_quote; // inherit Disc_quote's constructors
    double net_price(std::size_t) const;
};
  • For constructor, a using declaration causes the compiler to generate a derived constructor corresponding to each constructor in the base. That is, for each constructor in the base class, the compiler generates a constructor in the derived class that has the same parameter list.
  • These compiler-generated constructors have the form
    `derived(parameters) : base(args) {}
    derived is the name of the derived class; base is the name of the base class; parameters is the parameter list of the constructor; args pass the parameters from the derived constructor to the base constructor.
    For Bulk_quote class, the inherited constructor is equivalent to
Bulk_quote(const string& book, double price, size_t qty, double disc):
    Disc_quote(book, price, qty, disc) {}
  • If the derived class has any data members of its own, those members are default initialized(7.1.4).

Characteristics of an Inherited Constructor

  • A constructor using declaration does not change the access level of the inherited constructor(s). Regardless of where the using declaration appears, a private constructor in the base is a private constructor in the derived; similarly for protected and public constructors.
  • A using declaration can’t specify explicit or constexpr. If a constructor in the base is explicit(7.5.4) or constexpr(7.5.6), the inherited constructor has the same property.
  • If a base-class constructor has default arguments(6.5.1), those arguments are not inherited. The derived class gets multiple inherited constructors in which each parameter with a default argument is successively omitted. For example, if the base has a constructor with two parameters and the second has a default, the derived class will obtain two constructors: one with both parameters(no default argument) and a second constructor with a single parameter corresponding to the left-most, non-defaulted parameter in the base class.
  • If a base class has several constructors, the derived class inherits each of the constructors from its base class with two exceptions.
    1. A derived class can inherit some constructors and define its own versions of other constructors. If the derived class defines a constructor with the same parameters as a constructor in the base, then that constructor is not inherited. The one defined in the derived class is used in place of the inherited constructor.
    2. The default, copy, and move constructors are not inherited. These constructors are synthesized using the normal rules. An inherited constructor is not treated as a user-defined constructor. So, a class that contains only inherited constructors will have a synthesized default constructor.

Exercises Section 15.7.4

Exercise 15.27

Redefine your Bulk_quote class to inherit its constructors.

  • Omit.

15.8. Containers and Inheritance

  • When we use a container to store objects from an inheritance hierarchy, we must store those objects indirectly because there is no way to define a container that holds elements of differing types.
  • Assume to define a vector to hold several books that a customer wants to buy.
    1. Can’t use a vector that holds Bulk_quote objects since can’t convert Quote objects to Bulk_quote(15.2.3).
    2. Can’t use a vector that holds Quote objects. Though we can put Bulk_quote objects into the container, those objects would no longer be Bulk_quote objects:
vector basket;
basket.push_back(Quote("0-201-82470-1", 50));
// ok, but copies only the Quote part of the object into basket
basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, .25));
// calls version defined by Quote, prints 750, i.e., 15 * $50
cout << basket.back().net_price(15) << endl;
  • The elements in basket are Quote objects, so when we add Bulk_quote objects to vector, its derived part is ignored(15.2.3).
    Because derived objects are sliced down when assigned to a base-type object, containers and types related by inheritance do not mix well.

Put(Smart) Pointers, Not Objects, in Containers

  • When we need a container that holds objects related by inheritance, we should define the container to hold pointers(preferably smart pointers(12.1)) to the base class.
vector<shared_ptr> basket;
basket.push_back(make_shared("0-201-82470-1", 50));
basket.push_back(make_shared("0-201-54848-8", 50, 10, .25));
// calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount
cout << basket.back()->net_price(15) << endl;
  • Because basket holds shared_ptrs, we must dereference the value returned by basket.back() to get the object on which to run net_price. The version of net_price that is called depends on the dynamic type of the object to which that pointer points.
  • We can convert a smart pointer to a derived type to a smart pointer to an base-class type, make_shared returns a shared_ptr object, which is converted to shared_ptr when we call push_back. So, all elements of basket have the same type.

Exercises Section 15.8

Exercise 15.28

Define a vector to hold Quote objects but put Bulk_quote objects into that vector. Compute the total net_price of all the elements in the vector.

  • Omit.

Exercise 15.29

Repeat your program, but this time store shared_ptrs to objects of type Quote. Explain any discrepancy in the sum generated by the this version and the previous program. If there is no discrepancy, explain why there isn’t one.

  • Omit.

15.8.1. Writing a Basket Class(Omit)

15.9. Text Queries Revisited(Omit)

Chapter Summary

  • Inheritance lets us write new classes that share behavior with their base class(es) but override or add to that behavior as needed.
    Dynamic binding lets us ignore type differences by choosing, at run time, which version of a function to run based on an objects dynamic type.
  • Dynamic binding applies only to functions declared as virtual and called through a reference or pointer.
  • Because a derived-class object contains a subobject corresponding to each of its base classes, we can convert a reference or pointer to a derived-class type to a reference or pointer to an accessible base class.
  • Inherited objects are constructed, copied, moved, and assigned by constructing, copying, moving, and assigning the base part(s) of the object before handling the derived part.
    Destructors execute in the opposite order; the derived type is destroyed first, followed by destructors for the base-class subobjects.
  • Base classes should define a virtual destructor even if the class has no need for a destructor. The destructor must be virtual if a pointer to a base is ever deleted when it actually addresses a derived-class object.
  • A derived class specifies a protection level for each of its base class(es). Members of a public base are part of the interface of the derived class; members of a private base are inaccessible; members of a protected base are accessible to classes that derive from the derived class but not to users of the derived class.

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

你可能感兴趣的:(C++,Primer-第5版)