2021-09-06 learning record of C++ Primer: Chapter 15

Chapter 15 object oriented programming

15.1 OOP: Overview

The core idea of Object-Oriented Programming is:

  1. Data abstraction (Chapter 7)
  2. inherit
  3. Dynamic binding

(1) Inherit

Base class: Quote, which means books sold at original price.

Derived class: Bulk_quote, which means books that can be sold at a discount.

These classes will contain the following two member functions:

  • isbn(), returns the ISBN number of the book
  • net_price(size_t) , return the actual selling price of the book

In C + +, the base class distinguishes type related functions from functions directly inherited by derived classes without change. For some functions, the base class wants its derived classes to customize their own versions. At this time, the base class declares these functions as virtual functions. Therefore, the Quote class is written as:

class Quote 
{
public:
    std::string isbn() const;
    virtual double net_price(std::size_t n) const;    // Virtual indicates that this is a virtual function
};

A derived class must explicitly indicate which base class (s) it inherits from by using a class derivation list.

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

A derived class must declare all redefined virtual functions inside it. Derived classes can precede such functions with the virtual keyword, but they don't have to.

The new C++11 standard allows a derived class to explicitly indicate which member function it will use to rewrite the virtual function of the base class. The specific measure is to add an override keyword after the formal parameter list of the function.

(2) Dynamic binding

By using dynamic binding, we can handle Quote and bulk separately with the same piece of code_ Object of Quote.

The following function prints the total cost:

// Calculate and print the cost of selling a given number of books 
double print_total(ostream &os, const Quote &item, size_t n)
{
    // Call quote:: Net Based on the object type of the item parameter passed in_ price
    // Or Bulk_quote::net_price 
    double ret = item.net_price(n);
    
    //Call Quote::isbn
    os << "ISBN: " << item.isbn() << " # sold:" << n << " total due: " << ret << endl; 
    
    return ret;
}
  1. Because the function print_ The item parameter of total is a reference to the base class Quote, so we can call this function using the object of the base class Quote or the derived class bulk_ The object of Quote calls it

  2. Because print_total is a call to net using a reference type_ The price function is, so print is actually passed in_ The object type of total will determine the execution of net_ Which version of price:

    // The type of basic is quote; The type of bulk is Bulk_quote
    print_total(cout, basic, 20);    // Net calling Quote_ price
    print_total(cout, bulk, 20);     // Call Bulk_quote's net_price
    

Because in the above process, the running version of the function is determined by the arguments, that is, the version of the function is selected at runtime, dynamic binding is sometimes called runtime binding.

15.2 defining base and derived classes

15.2.1 define base class
class Quote 
{ 
public:
    Quote() = default;
    Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { } 
    std::string isbn() const { return bookNo; }
    // Returns the total sales of a given number of books
    // Derived classes are responsible for rewriting and using different discount calculation algorithms
    virtual double net_price(std::size_t n) const { return n * price; }
    virtual ~Quote() = default;     // Dynamically bind destructors 
private:
    std::string bookNo;             // ISBN number of the book
protected:
    double price = 0.0;             // Represents the price without discount under normal conditions
};

Base classes should usually define a virtual destructor, even if the function does not perform any actual operations.

(1) Member functions and inheritance

In C + +, the base class must distinguish between its two member functions:

  • One is the function that the base class wants its derived class to override
  • One is the function that the base class wants the derived class to inherit directly without changing.

For the former, the base class usually defines it as a virtual function. When we call a virtual function with a pointer or reference, the call will be bound dynamically. Depending on the object type to which the reference or pointer is bound, the call may execute the version of the base class or the version of a derived class.

Any non static function outside the constructor can be a virtual function. The keyword virtual can only appear before the declaration statement inside the class and cannot be used for function definitions outside the class. If the base class declares a function as a virtual function, the function is also implicitly a virtual function in the derived class. If a member function is not declared as a virtual function, its resolution occurs at compile time rather than run time.

(2) Access control and inheritance

Protected: the derived class has access to the protected members of the base class, while other users are prohibited from accessing.

15.2.2 defining derived classes
// Bulk_quote inherits from quote
class Bulk_quote : public Quote 
{ 
public:
    Bulk_quote() = default;
    Bulk_quote(const std::string&, double, std::size_t, double);
    // Override the function version of the base class to implement a discount policy based on bulk purchases 
    double net_price(std::size_t) const override; 
private:
    std::size_t min_qty = 0;    // Minimum purchase quantity subject to discount policy
    double discount = 0.0;      // Discount amount in decimal places
};

Most classes inherit from only one class, and derived classes often (but not always) override the virtual functions it inherits.

(1) Type conversion of derived class objects and derived classes to base classes

The C + + standard does not specify how the objects of derived classes are distributed in memory, but we can think of bulk_ The object of quote contains two parts as shown in the figure.

Because the derived class object contains components corresponding to its base class, we can use the derived class object as a base class object, and we can also bind the pointer or reference of the base class to the base class part of the derived class object.

Quote item;              // Base class object
Bulk_quote bulk;         // derived class object 
Quote *p = &item;        // p points to the Quote object
p = &bulk;               // p points to the Quote part of the bulk 
Quote &r = bulk;         // r is bound to the Quote part of the bulk

This conversion is often called type conversion from derived class to base class, and the compiler implicitly performs this conversion.

(2) Derived class constructor

We want each class to control its own member initialization process. Therefore, the derived class must use the constructor of the base class to initialize its base class part.

//Same as before
Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc) { }

Otherwise, the base class part of the derived class object performs default initialization like a data member.

(3) Inheritance and static members

If the base class defines a static member, only the unique definition of the member exists in the whole inheritance system. No matter how many derived classes are derived from the base class, there are only unique instances for each static member.

Static members follow general access control rules. If the member in the base class is private, the derived class does not have access to it.

(4) Declaration of derived classes

class Bulk_quote : public Quote;    // Error; Derived lists cannot appear here 
class Bulk_quote;                   // correct; The correct way to declare a derived class

If we want to use a class as a base class, the class must already be defined, not just declared.

(5) Prevent inheritance

The new C++11 standard provides a method to prevent inheritance, that is, the class name is followed by the keyword final:

class NoDerived final { /* */ };       // NoDerived cannot be used as a base class 
class Base { /* */ };
// Last is final; We can't inherit last 
class Last final : Base { /* */ };     // Last cannot be used as a base class
class Bad : NoDerived { /* */ };       // Error: NoDerived is final
class Bad2 : Last { /**/ };            // Error: Last is final
15.2.3 type conversion and inheritance

We can bind the pointer or reference of the base class to the derived class object.

When using a reference (or pointer) to a base class, we don't actually know the real type of the object to which the reference (or pointer) is bound. The object may be an object of a base class or a derived class.

(1) Static and dynamic types

  1. The static type of an expression is always known at compile time. It is the type when the variable is declared or the type generated by the expression.

  2. Dynamic types are the types of objects in memory represented by variables or expressions. Dynamic types are not known until runtime.

(2) There is no implicit type conversion from a base class to a derived class

The reason why there is type conversion from derived class to base class is that each derived class object contains a base class part, and the reference or pointer of the base class can be bound to the base class part. The object of a base class can exist in an independent form or as part of a derived class object. If the base class object is not part of a derived class object, it contains only members of the base class definition, not members of the derived class definition.

Because the object of a base class may or may not be part of a derived class object, there is no automatic type conversion from a base class to a derived class.

If there are one or more virtual functions in the base class, we can use dynamic_cast requests a type conversion whose security checks will be performed at run time. If we know that the conversion from a base class to a derived class is safe, we can use static_cast to force the compiler's checking work to be overwritten.

(3) There is no type conversion between objects

When assigning a derived class to a base class, the corresponding function is called instead of type conversion:

Bulk_quote bulk;       // derived class object 
Quote item(bulk);      // Use the quote:: Quote (const quote &) constructor
item = bulk;           // Call quote:: operator = (const quote &)

When constructing an item, run the Quote's copy constructor. This function can only handle bookNo and price. It is responsible for copying the members of the Quote part of the bulk and ignoring the bulk in the bulk_ Members of the Quote section. Similarly, for the operation of assigning bulk to item, only the members of the Quote part of bulk are assigned to item.

Because bulk will be ignored in the above process_ Quote part, so we can say bulk of bulk_ The quote part was cut off.

Keywords: C++

Added by $kevan on Tue, 07 Sep 2021 00:56:51 +0300