Object-Oriented Programming

Object-oriented programming is based on three fundamental concepts: data abstraction, which we covered in Chapter 3, and inheritance and dynamic binding, which we’ll cover in this chapter.

OOP: An Overview

The key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding. Using data abstraction, we can define classes that separate interface from implementation (Chapter 3). Through inheritance, we can define classes that model the relationships among similar types. Through dynamic binding, we can use objects of these types while ignoring the details of how they differ.

Inheritance

Classes related by inheritance form a hierarchy. Typically there is a base class at the root of the hierarchy, from which the other classes inherit, directly or indirectly. 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’ll define a class named Quote, which will be the base class of our hierarchy. A Quote object will represent undiscounted books. From Quote we will inherit a second class, named Bulk_quote, to represent books that can be sold with a quantity discount.

These classes will have the following two member functions:

  1. isbn(), which will return the ISBN. This operation does not depend on the specifics of the inherited class(es); it will be defined only in class Quote.
  2. net_price(size_t), which will 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.

In C++, a base class distinguishes functions that are type dependent from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to define for themselves. Using this knowledge, we can start to write our Quote class:

class Quote {
public:
    std::string isbn() const;
    virtual double net_price(std::size_t n) const;
};

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

A derived class must specify the class(es) from which it intends to inherit. It does so in 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.

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. A derived class may include the virtual keyword on these functions but is not required to do so.

Dynamic Binding

Through dynamic binding, we can use the same code to process objects of either type Quote or Bulk_quote interchangeably. For example, 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;
}

Defining Base and Derived Classes

In many, but not all, ways base and derived classes are defined like other classes we have already seen. In this section, we’ll cover the basic features used to define classes related by inheritance.

Defining a Base Class

We’ll start by completing the definition of our Quote class:

// in Quote.h
#pragma once
#include<string>
class Quote {
public:
    Quote() = default;
    Quote(std::string s, double p) :bookNo(s), price(p) {}
    virtual ~Quote() = default;

    std::string isbn();
    virtual double net_price(std::size_t n);

private:
    std::string bookNo;
protected:
    double price = 0.0;
};


// in Quote.cpp
#include "Quote.h"

std::string Quote::isbn() {
    return bookNo;
}

double Quote::net_price(std::size_t n) {
    return n * price;
}

Derived classes inherit the members of their base class. However, a derived class needs to be able to provide its own definition for operations, such as net_price, that are type dependent. In such cases, the derived class needs to override the definition it inherits from the base class, by providing its own definition.

In C++, a base class must distinguish the functions it expects its derived classes to override from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to override.

Member functions that are not declared as virtual are resolved at compile time, not run time. For the isbn member, this is exactly the behavior we want. The isbn function does not depend on the details of a derived type.

A derived class inherits the members defined in its base class. However, the member functions in a derived class may not necessarily access the members that are inherited from the base class. Like any other code that uses the base class, a derived class may access the public members of its base class but may not access the private members. However, sometimes a base class has members that it wants to let its
derived classes use while still prohibiting access to those same members by other users. We specify such members after a protected access specifier.

Defining a Derived Class

A derived class must specify from which class(es) it inherits. It does so 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.

When the derivation is public, the public members of the base class become part of the interface of the derived class as well. In addition, we can bind an object of a publicly derived type to a pointer or reference to the base type. Because we used public in the derivation list, the interface to Bulk_quote implicitly contains the isbn function, and we may use a Bulk_quote object where a pointer or reference to Quote is expected.

#pragma once
#include<string>
class Quote {
public:
    Quote() = default;
    // isbn, price , minimum quantity, discount
    Quote(std::string s, double p) :bookNo(s), price(p) {}
    virtual ~Quote() = default;

    std::string isbn();
    virtual double net_price(std::size_t n) const;

private:
    std::string bookNo;
protected:
    double price = 0.0;
};

Our Bulk_quote class inherits the isbn function and the bookNo and price data members of its Quote base class. It defines its own version of net_price and has two additional data members, min_qty and discount. These members specify the minimum quantity and the discount to apply once that number of copies are purchased.

Derived classes frequently, but not always, override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class.

Although a derived object contains members that it inherits from its base, it cannot directly initialize those members. Like any other code that creates an object of the base-class type, a derived class must use a base-class constructor to initialize its base-class part.

#include "Bulk_quote.h"

Bulk_quote::Bulk_quote(const std::string& is, double pr, std::size_t min_n, double dis)
    :Quote(is, pr), min_qty(min_n), discount(dis) {
}

double Bulk_quote::net_price(std::size_t n) const {
    if (n >= min_qty) {
        return (1 - discount) * price * n;
    }
    return price * n;
}

Some details

A derived class is declared like any other class. 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

A class must be defined, not just declared, before we can use it as a base class:

class Quote; // declared but not defined
// error: Quote must be defined
class Bulk_quote : public Quote { ... };

Sometimes we define a class that we don’t want others to inherit from. Or we might define a class for which we don’t want to think about whether it is appropriate as a base class. Under the new standard, 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

Conversions and Inheritance

Ordinarily, we can bind a reference or a pointer only to an object that has the same type as the corresponding reference or pointer or to a type that involves an acceptable const conversion. Classes
related by inheritance are an important exception: We can bind a pointer or reference to a base-class type to an object of a type derived from that base class. For example, we can use a Quote& to refer to a Bulk_quote object, and we can assign the address of a Bulk_quote object to a Quote*.

When we use types related by inheritance, we often need to distinguish between the static type of a variable or other expression and the dynamic type of the object that expression represents. The static type of an expression is always known at compile time—it is the type with which a variable is declared or that an expression yields. The dynamic type is the type of the object in memory that the variable or expression represents. The dynamic type may not be known until run time.

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. There is no similar guarantee for base-class objects.

Virtual Functions

When a virtual function is called through a reference or pointer, the compiler generates code to decide at run time which function to call. The function that is called is the one that corresponds to the dynamic type of the object bound to that pointer or reference.

As an example, you can run the next code in your computer:

#include"Bulk_quote.h"
#include<iostream>


int main() {
    std::string s = "54742675";
    Bulk_quote b(s, 10, 8, 0.2);
    // by refreance, out put is 80
    Quote& p1 = b;
    std::cout << p1.net_price(10) << std::endl;
    // by pointer, out put is 80
    Quote* p2 = &b;
    std::cout << p2->net_price(10) << std::endl;
    // either, out put is 100
    Quote p3 = b;
    std::cout << p3.net_price(10) << std::endl;
    return 0;
}

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 plain—nonreference and nonpointer—type, that call is bound at compile time.

When a derived class overrides a virtual function, it may, but is not required to, repeat the virtual keyword. 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 exactly the same parameter type and return type as the base-class function that it overrides.

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. The compiler considers such a function to be independent from the base-class function. In such cases, 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 class but made a mistake in specifying the parameter list.

Under the new standard we can specify override on a virtual function in a derived class. Doing so makes our intention clear and (more importantly) enlists the compiler in finding such problems for us. The compiler will reject a program if a function marked override 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
};

We can also 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
};

Some detail

Like any other function, a virtual function can have default arguments. 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. That is, 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.

In some cases, we want to prevent dynamic binding of a call to a virtual function; we want to force the call to use a particular version of that virtual. We can use the scope operator to do so. For example, this code:

Quote* p2 = &b;
std::cout << p2->Quote::net_price(10) << std::endl;

Abstract Base Classes

Imagine that we want to extend our bookstore classes to support several discount strategies. In addition to a bulk discount, we might offer a discount for purchases up to a certain quantity and then charge the full price thereafter. Or we might offer a discount for purchases above a certain limit but not for purchases up to that limit.

Each of these discount strategies is the same in that it requires a quantity and a discount amount. We might support these differing strategies by defining a new class named Disc_quote to store the quantity and the discount amount. Classes, such as Bulk_item, that represent a specific discount strategy will inherit from Disc_quote. Each of the derived classes will implement its discount strategy by defining its own version of net_price.

Before we can define our Disc_Quote class, we have to decide what to do about net_price. Our Disc_quote class doesn’t correspond to any particular discount strategy; there is no meaning to ascribe to net_price for this class. We could define Disc_quote without its own version of net_price. In this case, Disc_quote would inherit net_price from Quote.

However, this design would make it possible for our users to write nonsensical code. A user could create an object of type Disc_quote by supplying a quantity and a discount rate. Passing that Disc_quote object to a function such as print_total would use the Quote version of net_price. The calculated price would not include the discount that was supplied when the object was created. That state of affairs
makes no sense.

Pure Virtual Functions

Thinking about the question in this detail reveals that our problem is not just that we don’t know how to define net_price. In practice, we’d like to prevent users from creating Disc_quote objects at all.

We can enforce this design intent—and make it clear that there is no meaning for net_price—by defining net_price as a pure virtual function. Unlike ordinary virtuals, a pure virtual function does not have to be defined. We specify that a virtual function is a pure virtual by writing = 0 in place of a function body.

#pragma once
#include "Quote.h"
class Disc_Quote : public Quote {
public:
    Disc_Quote() = default;
    Disc_Quote(const std::string& book, double price,
        std::size_t qty, double disc) :
        Quote(book, price),
        quantity(qty), discount(disc) {
    }
    double net_price(std::size_t) const = 0;
protected:
    std::size_t quantity = 0; // purchase size for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

A class containing (or inheriting without overriding) a pure virtual function is an abstract base class. An abstract base class defines an interface for subsequent classes to override. We cannot (directly) create objects of a type that is an abstract base class. Because Disc_quote defines net_price as a pure virtual, we cannot define objects of type Disc_quote. We can define objects of classes that inherit from Disc_quote, so long as those classes override net_price:

Disc_Quote discounted; // error: can't define a Disc_quote object

A Derived Class Constructor Initializes Its Direct Base Class Only

Now we can reimplement Bulk_quote to inherit from Disc_quote rather than inheriting directly from Quote:

#pragma once
#include "Disc_Quote.h"
class Bulk_quote : public Disc_Quote {
public:
    Bulk_quote() = default;
    // isbn, price , minimum quantity, discount
    Bulk_quote(const std::string&, double, std::size_t, double);
    double net_price(std::size_t n) const override;
};

#include "Bulk_quote.h"

// isbn, price , minimum quantity, discount
Bulk_quote::Bulk_quote(const std::string& is, double pr, std::size_t min_n, double dis)
    :Disc_Quote(is, pr, min_n, dis) {
}

double Bulk_quote::net_price(std::size_t n) const {
    if (n >= quantity) {
        return (1 - discount) * price * n;
    }
    return price * n;
}

Access Control and Inheritance

Just as each class controls the initialization of its own members, each class also controls whether its members are accessible to a derived class.

protected Members

As we’ve seen, a class uses protected for those members that it is willing to share with its derived classes but wants to protect from general access. The protected specifier can be thought of as a blend of private and public:

  • Like private, protected members are inaccessible to users of the class.
  • Like public, protected members are accessible to members and friends of classes derived from this class.

In addition, protected has another important property:

  • 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.

public, private, and protected Inheritance

Access to a member that a class inherits is controlled by a combination of the access specifier for that member in the base class, and the access specifier in the derivation list of the derived class. As an example, consider the following hierarchy:

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; }
};

The derivation access specifier has no effect on whether members (and friends) of a derived class may access the members of its own direct base class. Access to the members of a base class is controlled by the access specifiers in the base class itself. Both Pub_Derv and Priv_Derv may access the protected member prot_mem. Neither may access the private member priv_mem.

The purpose of the derivation access specifier is to control the access that users of the derived class—including other classes derived from the derived class—have to the members inherited from Base:

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

The derivation access specifier used by a derived class also controls access from classes that inherit from that 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: Base::prot_mem is private in Priv_Derv
    int use_base() { return prot_mem; }
};

Classes derived from Pub_Derv may access prot_mem from Base because that member remains a protected member in Pub_Derv. In contrast, classes derived from Priv_Derv have no such access. To them, all the members that Priv_Derv inherited from Base are private.

Friendship and Inheritance

Just as friendship is not transitive, friendship is also not inherited. Friends of the base have no special access to members of its derived classes, and friends of a derived class 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
};

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:

class Base {
public:
    std::size_t size() const { return n; }
protected:
    std::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;
};

Because Derived uses private inheritance, the inherited members, size and n, are (by default) private members of Derived. The using declarations adjust the accessibility of these members. Users of Derived can access the size member, and classes subsequently derived from Derived can access n.

In previous we saw that classes defined with the struct and class keywords have different default access specifiers. Similarly, the default derivation specifier depends on which keyword is used to define a derived class. By default, a derived class defined with the class keyword has private inheritance; a derived class defined with struct has public inheritance

Class Scope under Inheritance

Each class defines its own scope within which its members are defined. Under inheritance, the scope of a derived class is nested 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 of an object, reference, or pointer determines which members of that object are visible. Even when the static and dynamic types might differ (as can happen when a reference or pointer to a base class is used), the static type determines what members can be used. As an example, we might add a member to the Disc_quote class that returns a pair holding the minimum (or maximum) quantity and the discounted price:

class Disc_quote : public Quote {
public:
    std::pair<size_t, double> 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:

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*

Even though bulk has a member named discount_policy, that member is not visible through itemP. The type of itemP 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

Like any other scope, a derived class can reuse a name defined in one of its direct or indirect base classes. As usual, names defined in an inner scope hide uses of that name in the outer scope:

struct Base {
    Base() : mem(0) {}
public:
    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
public:
    int mem; // hides mem in the base
};

We use the :: select scope:

Derived p(19);
std::cout << p.mem << std::endl;      // output is 19
std::cout << p.Base::mem << std::endl;// output is 0

As we’ve seen, functions declared in an inner scope do not overload functions declared in an outer scope. As a result, functions defined in a derived class do not overload members defined in its base class(es). As in any other scope, 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

Virtual Functions and Scope

We can now understand why virtual functions must have the same parameter list in the base and derived classes. 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. For example:

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
};

Constructors and Copy Control

Like any other class, a class in an inheritance hierarchy controls what happens when objects of its type are created, copied, moved, assigned, or destroyed. As for any other class, if a class (base or derived) does not itself define one of the copy-control operations, the compiler will synthesize that operation. Also, as usual, the synthesized version of any of these members might be a deleted function.

Virtual Destructors

The primary direct impact that inheritance has on copy control for a base class is that a base class generally should define a virtual destructor. The destructor needs to be virtual to allow objects in the inheritance hierarchy to be dynamically allocated.

Recall that the destructor is run when we delete a pointer to a dynamically allocated object. If that pointer points to a type in an inheritance hierarchy, it is possible that the static type of the pointer might differ from the dynamic type of the object being destroyed. For example, if we delete a pointer of type Quote*, that pointer might point at a Bulk_quote object.

If the pointer points at a Bulk_quote, the compiler has to know that it should run the Bulk_quote destructor. As with any other function, we arrange to run the proper destructor by defining the destructor as virtual in the base class.

Like any other virtual, the virtual nature of the destructor is inherited. Thus, 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

Synthesized Copy Control and Inheritance

The synthesized copy-control members in a base or a derived class execute like any other synthesized constructor, assignment operator, or destructor: They memberwise initialize, assign, or destroy the members of the class itself. In addition, these synthesized members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class.

the synthesized Bulk_quote copy constructor uses the (synthesized) Disc_quote copy constructor, which uses the (synthesized) Quote copy constructor. The Quote copy constructor copies the bookNo and price members; and the Disc_Quote copy constructor copies the qty and discount members.

The synthesized default constructor, or any of the 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:

  1. If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible, 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. As usual, 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(std::move(d)); // error: implicitly uses D's deleted copy constructor

Derived-Class Copy-Control Members

When we define a copy or move constructor 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 */ { /* ... */
    }
};

Like the copy and move constructors, a derived-class assignment operator, 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;
}

Recall that the data members of an object are implicitly destroyed after the destructor body completes. Similarly, the base-class parts of an object are also implicitly destroyed. As a result, unlike the constructors and assignment operators, 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.

Inherited Constructors

Under the new standard, a derived class can reuse the constructors defined by its direct base class. Although, as we’ll see, such constructors are not inherited in the normal sense of that term, it is nonetheless common to refer to such constructors as “inherited.” For the same reasons that 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. As an example, we can redefine our Bulk_quote class to inherit its constructors from Disc_quote:

class Bulk_quote : public Disc_quote {
public:
    using Disc_quote::Disc_quote; // inherit Disc_quote's constructors
    double net_price(std::size_t) const;
};

Containers and Inheritance

When we use a container to store objects from an inheritance hierarchy, we generally must store those objects indirectly. We cannot put objects of types related by inheritance directly into a container, because there is no way to define a container that holds elements of differing types.

As an example, assume we want to define a vector to hold several books that a customer wants to buy. It should be easy to see that we can’t use a vector that holds Bulk_quote objects. We can’t convert Quote objects to Bulk_quote, so we wouldn’t be able to put Quote objects into that vector.

It may be somewhat less obvious that we also can’t use a vector that holds objects of type Quote. In this case, we can put Bulk_quote objects into the container. However, those objects would no longer be Bulk_quote objects:

vector<Quote> 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. When we add a Bulk_quote object to the vector its derived part is ignored.

When we need a container that holds objects related by inheritance, we typically define the container to hold pointers (preferably smart pointers) to the base class. As usual, the dynamic type of the object to which those pointers point might be the base-class type or a type derived from that base:

vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-82470-1", 50));
basket.push_back(
make_shared<Bulk_quote>("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;

Writing a Basket Class

One of the ironies of object-oriented programming in C++ is that we cannot use objects directly to support it. Instead, we must use pointers and references. Because pointers impose complexity on our programs, we often define auxiliary classes to help manage that complexity. We’ll start by defining a class to represent a basket:

// Basket.h
#pragma once
#include<memory>
#include"Quote.h"
#include<set>

class Basket {
public:
    // Basket uses synthesized default constructor and copy-control members
    void add_item(const std::shared_ptr<Quote>& sale) {
        items.insert(sale);
    }
    // prints the total price for each book and the overall total for all items in the basket
    double total_receipt(std::ostream&) const;
private:
    static bool compare(const std::shared_ptr<Quote>& lhs,
        const std::shared_ptr<Quote>& rhs) {
        return lhs->isbn() < rhs->isbn();
    }
    std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{ compare };
};

The elements in our multiset are shared_ptrs and there is no less-than operator for shared_ptr. As a result, we must provide our own comparison operation to order the elements. Here, we define a private
static member, named compare, that compares the isbns of the objects to which the shared_ptrs point. We initialize our multiset to use this comparison function through an in-class initializer.

The Basket class defines only two operations. We defined the add_item member inside the class. That member takes a shared_ptr to a dynamically allocated Quote and puts that shared_ptr into the multiset. The second member, total_receipt, prints an itemized bill for the contents of the basket and returns the price for all the items in the basket:

// Basket.cpp
#include "Basket.h"

double print_total(std::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 << std::endl;
    return ret;
}

double Basket::total_receipt(std::ostream& os) const {
    double sum = 0.0;
    // holds the running total
    // iter refers to the first element in a batch of elements with the same ISBN
    // upper_bound returns an iterator to the element just past the end of that batch
    for (auto iter = items.cbegin();
        iter != items.cend();
        iter = items.upper_bound(*iter)) {
        // we know there's at least one element with this key in the Basket
        // print the line item for this book
        sum += print_total(os, **iter, items.count(*iter));
    }
    os << "Total Sale: " << sum << std::endl; // print the final overall total
    return sum;
}

The interesting bit is the “increment” expression in the for. Rather than the usual loop that reads each element, we advance iter to refer to the next key. We skip over all the elements that match the current key by calling upper_bound. The call to upper_bound returns the iterator that refers to the element just past the last one with the same key as in iter. The iterator we get back denotes either the end of the set or the next book.

Users of Basket still have to deal with dynamic memory, because add_item takes a shared_ptr. As a result, users have to write code such as

Basket b;
b.add_item(std::make_shared<Quote>("111", 50));
b.add_item(std::make_shared<Quote>("124", 60));

Our next step will be to redefine add_item so that it takes a Quote object instead of a shared_ptr. This new version of add_item will handle the memory allocation so that our users no longer need to do so. We’ll define two versions, one that will copy its given object and the other that will move from it:

void add_item(const Quote& sale); // copy the given object
void add_item(Quote&& sale); // move the given object

The only problem is that add_item doesn’t know what type to allocate. When it does its memory allocation, add_item will copy (or move) its sale parameter. We’ll solve this problem by giving our Quote classes a virtual member that allocates a copy of itself.

class Quote {
public:
    // virtual function to return a dynamically allocated copy of itself
    // these members use reference qualifiers; see §13.6.3 (p. 546)
    virtual Quote* clone() const& {
        return new
            Quote(*this);
    }
    virtual Quote* clone()&& {
        return new
            Quote(std::move(*this));
    }
    // other members as before
};
class Bulk_quote : public Quote {
    Bulk_quote* clone() const& {
        return new
            Bulk_quote(*this);
    }
    Bulk_quote* clone()&& {
        return new
            Bulk_quote(std::move(*this));
    }
    // other members as before
};

Using clone, it is easy to write our new versions of add_item:

class Basket {
public:
    void add_item(const Quote& sale) // copy the given object
    {
        items.insert(std::shared_ptr<Quote>(sale.clone()));
    }
    void add_item(Quote&& sale) // move the given object
    {
        items.insert(
            std::shared_ptr<Quote>(std::move(sale).clone()));
    }
    // other members as before
};

life is perfact