[C++ Primer Plus] Chapter 14 Code Reuse in C++

Some concepts:

has-a relationship : Typically, containment, private inheritance, and protected inheritance are used to implement a has-a relationship, ie a new class will contain objects of another class.
A subobject represents an object added through inheritance or containment.
Multiple inheritance (MI) uses inheritance from multiple base classes. Multiple inheritance enables new classes to be derived from two or more base classes, combining the functionality of the base classes.
Virtual base class : Objects derived from multiple classes (their base classes are the same) inherit only one base class object.

Ways C++ promotes code reuse:

  1. public inheritance. Used to implement the is-a relationship
  2. Use a class member that is itself an object of another class. This approach is called containment, composition, or layering.
  3. Use private or protected inheritance.

The difference between containment and private inheritance:

  1. Contains adds the object to the class as a named member object. Private inheritance adds the object to the class as an unnamed inherited object.
  2. Initializes the base class component: Constructor member initializer list that contains member names. Privately inherited constructor member initialization lists use the class name.
  3. Access to base class methods: Contains calling methods of the base class using member object names. Private inheritance uses the class name and the scope resolution operator to call methods of the base class.
  4. Access to base class objects: including using member object names. Private inheritance uses casts.
  5. Access to base class friend functions: Contains implicit calls to base class friend functions using member object names. Private inheritance can call the correct function by explicitly casting to the base class.
  6. Contains can contain multiple sub-objects of the same kind. Only one base class object can be used for private inheritance.
  7. Derived classes with private inheritance can redefine virtual functions, but containing classes cannot.
  8. In general, containment should be used to establish a has-a relationship; if the new class needs to access protected members of the original class, or need to redefine virtual functions, private inheritance should be used.
  9. Private inheritance can use the using statement to make the method of the base class available outside the derived class, but the using statement only applies to inheritance, not to inclusion.

14.1 Classes containing object members

For the representation of a student's test scores in various subjects:

  1. A fixed-length array can be used, which limits the length of the array;
  2. Can use dynamic memory allocation and provide a lot of support code;
  3. It is also possible to design a class that uses dynamic memory allocation to represent the array;
  4. You can also look up a class in the standard C++ library that can represent this kind of data. (valarray class)

Interface and implementation:
When using public inheritance, a class can inherit an interface and possibly an implementation (pure virtual functions of the base class provide the interface, but not the implementation). Obtaining an interface is part of an is-a relationship.
Using composition (containment), a class can get an implementation, but not an interface. Not inheriting an interface is part of a has-a relationship.
The member initialization list of the constructor:

  1. Initializes members of built-in types.
  2. Initializes the base class portion of the derived object (For inherited objects, the constructor uses the class name in the member initialization list to call a specific base class constructor. For member objects, the constructor uses the member name.)

What would happen without the initializer-list syntax?
C++ requires that all member objects of an inherited object be constructed before the rest of the object is constructed. Therefore, if you omit the initializer list, C++ will use the default constructor of the class to which the member object belongs.

valarray class

The template attribute means that when declaring an object, a specific data type must be specified. Therefore, when using the valarray class to declare an object, you need to add a pair of angle brackets after the identifier valarray, and include the required data type in it: The
valarray class is supported by the header file valarray.

  1. operator : accesses individual elements.
  2. size( ): Returns the number of elements contained.
  3. sum( ): Returns the sum of all elements.
  4. max( ): returns the largest element.
  5. min( ): Returns the smallest element.
#include <valarray>

double gpa[5] = {
    
    3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1; 			// an array of double, size 0
valarray<int> v2(8); 			// an array of 8 int elements
valarray<int> v3(10,8); 		// an array of 8 int elements, each set to 10
valarray<double> v4(gpa, 4);	// an array of 4 elements, initialized to the first 4 elements of gpa
valarray<int> v5 = {
    
    20, 32, 17, 9}; // C++11

Example: a class with object members

studentc.h


#ifndef PRIMERPLUS_STUDENTC_H
#define PRIMERPLUS_STUDENTC_H
#include <iostream>
#include <string>
#include <valarray>
using namespace std;

class Student
{
    
    
private:
    typedef valarray<double> ArrayDb;
    string name;
    ArrayDb scores;
    ostream & arr_out(ostream & os) const;
public:
    Student() : name("Null"), scores() {
    
    }
    explicit Student(const string & s) : name(s), scores() {
    
    }
    explicit Student(int n) : name("Null"), scores(n) {
    
    }
    Student(const string & s, int n) : name(s), scores(n) {
    
    }
    Student(const string & s, const ArrayDb & a) : name(s), scores(a) {
    
    }
    Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) {
    
    }
    ~Student() {
    
    }

    double Average() const;
    const string & Name() const;
    double & operator[](int i);
    double operator[](int i) const;

    friend istream & operator>>(istream & is, Student & stu);
    friend istream & getline(istream & is, Student & stu);
    friend ostream & operator<<(ostream & os, const Student & stu);
};

#endif //PRIMERPLUS_STUDENTC_H

studentc.cpp

#include "studentc.h"

double Student::Average() const
{
    
    
    if (scores.size() > 0)
        return scores.sum() / scores.size();
    else
        return 0;
}

const string & Student::Name() const
{
    
    
    return name;
}

double & Student::operator[](int i)
{
    
    
    return scores[i];
}

double Student::operator[](int i) const
{
    
    
    return scores[i];
}

// private method
ostream & Student::arr_out(ostream & os) const
{
    
    
    int i;
    int lim = scores.size();
    if (lim > 0)
    {
    
    
        for (i = 0; i < lim; i++)
        {
    
    
            os << scores[i] << " ";
            if (i%5 == 4)
                os << endl;
        }
    }
    else
        os << "empty array.";
    return os;
}

// friend method
// use string version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
    
    
    is >> stu.name;
    return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
    
    
    getline(is, stu.name);
    return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    
    
    os << "Scores for " << stu.name << ":\n";
    stu.arr_out(os);
    return os;
}

usestudentc.cpp

#include "studentc.h"

void set(Student & sa, int n);

const int pupils = 3;
const int quizzes = 5;

int main()
{
    
    
    Student ada[pupils] =
            {
    
    Student(quizzes), Student(quizzes), Student(quizzes)};
    int i;
    for (i = 0; i < pupils; ++i)
        set(ada[i], quizzes);
    cout << "\nStudent List:\n";
    for (i = 0; i < pupils; ++i)
        cout << ada[i].Name() << endl;
    cout << "\nResults:";
    for (i = 0; i < pupils; ++i)
    {
    
    
        cout << endl << ada[i];
        cout << "average: " << ada[i].Average() << endl;
    }
    cout << "Done.\n";
    return 0;
}

void set(Student & sa, int n)
{
    
    
    cout << "Please enter the student's name: ";
    getline(cin, sa);
    cout << "Please enter " << n << " quiz scores:\n";
    for (int i = 0; i < n; i++)
        cin >> sa[i];
    while (cin.get() != '\n')
        continue;
}

14.2 Private inheritance

  1. C++ has another way to implement the has-a relationship—private inheritance.
  2. With public inheritance, the public methods of the base class become public methods of the derived class. In summary, derived classes will inherit the interface of the base class; this is part of the is-a relationship.
  3. With private inheritance, the public methods of the base class become private methods of the derived class. In summary, derived classes do not inherit the interface of the base class; this is part of the has-a relationship.
  4. The private members of the private base class can only be accessed through the member functions of the private base class;
  5. Public and protected members of a private base class can only be accessed through member functions of a private derived class;

Why do you need to cast when private inheritance accesses the friend function of the base class?

  1. In private inheritance, a reference or pointer to a derived class cannot be assigned to a base class reference or pointer without an explicit type conversion.
  2. Without type conversion, some (<< operator overloaded) friend function code would match the prototype, resulting in a recursive call.
  3. Another reason is that since this class is using multiple inheritance, the compiler will not be able to determine which base class to convert to if both base classes provide the function operator<<( ).

Example: An Example of Private Inheritance

students.h


#ifndef PRIMERPLUS_STUDENTI_H
#define PRIMERPLUS_STUDENTI_H
#include <iostream>
#include <cstring>
#include <valarray>
using namespace std;

class Student : private string, private valarray<double>
{
    
    
private:
    typedef valarray<double> ArrayDb;
    ostream & arr_out(ostream & os) const;
public:
    Student() : string("Null"), ArrayDb() {
    
    }    // 调用相应基类的构造函数
    explicit Student(const string & s) : string(s), ArrayDb() {
    
    }
    explicit Student(int n) : string("Null"), ArrayDb(n) {
    
    }
    Student(const string & s, int n) : string(s), ArrayDb(n) {
    
    }
    Student(const string & s, const ArrayDb & a) : string(s), ArrayDb(a) {
    
    }
    Student(const char * str, const double * pd, int n) : string(str), ArrayDb(pd, n) {
    
    }
    ~Student() {
    
    }

    double Average() const;
    double & operator[](int i);
    double operator[](int i) const;
    const string & Name() const;

    friend istream & operator>>(istream & is, Student & stu);
    friend istream & getline(istream & is, Student & stu);
    friend ostream & operator<<(ostream & os, const Student & stu); // 这里注意加const
};

#endif //PRIMERPLUS_STUDENTI_H

students.cpp

#include "studenti.h"

double Student::Average() const
{
    
    
    if (ArrayDb::size() > 0)    // 访问基类方法:使用类名和作用域解析符
        return ArrayDb::sum() / ArrayDb::size();
    else
        return 0;
}

const string & Student::Name() const
{
    
    
    return (const string &) *this;  // 访问基类对象:使用强制类型转换
}

double & Student::operator[](int i)
{
    
    
    return ArrayDb::operator[](i);
}

double Student::operator[](int i) const
{
    
    
    return ArrayDb::operator[](i);
}

// private method
ostream & Student::arr_out(ostream & os) const
{
    
    
    int i;
    int lim = ArrayDb::size();
    if (lim > 0)
    {
    
    
        for (i = 0; i < lim; i++)
        {
    
    
            os << ArrayDb::operator[](i) << " ";
            if (i%5 == 4)
                os << endl;
        }
    }
    else
        os << "empty array.";
    return os;
}

// friend method
// use string version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
    
    
    is >> (string &) stu;
    return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
    
    
    getline(is, (string &) stu);
    return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    
    
    os << "Scores for " << (const string &) stu << ":\n";
    stu.arr_out(os);
    return os;
}

protected inheritance

  1. Protected inheritance is a variant of private inheritance. Protected inheritance uses the keyword protected when listing base classes.
  2. When using protected inheritance, both public and protected members of the base class become protected members of the derived class.

Key Differences Between Private Inheritance and Protected Inheritance : (When another class is derived from a derived class)

  1. When using private inheritance, the third-generation class will not be able to use the interface of the base class, because the public method of the base class will become a private method in the derived class;
  2. When using protected inheritance, the public methods of the base class become protected in the second generation, so they can be used by the third generation derived classes.

Make base class methods available outside derived classes :

  1. Define a derived class method that uses this base class method.
  2. Wrap a function call in another function call, that is, use a using declaration (like a namespace) to indicate that a derived class can use a particular base class member, even if it is a private derivation.
    A using-declaration uses only member names—no parentheses, function signatures, or return types.
    A using-declaration applies only to inheritance, not to inclusion.
// 定义一个使用该基类方法的派生类方法。
double Student::sum() const 				// public Student method
{
    
    
	return std::valarray<double>::sum(); 	// use privately-inherited method
}

// 方法二
class Student : private std::string, private std::valarray<double>
{
    
    
    ...
    public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
	using std::valarray<double>::operator[];
    ...
};

14.3 Multiple inheritance

MI may bring many new problems to programmers. Two of the main issues are:

  1. Inheriting a method with the same name from two different base classes;
  2. Multiple instances of the same class inherit from two or more related base classes.

Example: An Example of Multiple Inheritance

Example: First define an abstract base class Worker, and use it to derive the Waiter class and Singer class. Then, you can use MI to derive the SingingWaiter class from the Waiter class and the Singer class.
Assume that SingingWaiter is first publicly derived from Singer and Waiter: because both Singer and Waiter inherit a Worker component, SingingWaiter will contain two Worker components.
Sets the base class pointer to the address of the base class object in the derived object. Assign the address of the derived class object to the base class pointer. Use type casting to specify objects

Worker * pw1 = (Waiter *) &ed; // the Worker in Waiter
Worker * pw2 = (Singer *) &ed; // the Worker in Singer

When C++ introduces multiple inheritance, it also introduces a new technology—virtual base class (virtual base class), which makes MI possible.
The virtual base class enables objects derived from multiple classes (their base classes are the same) to inherit only one base class object.
Worker can be used as a virtual base class for Singer and Waiter by using the keyword virtual in the class declaration (the order of virtual and public does not matter)

class Singer : virtual public Worker {
    
    ...};
class Waiter : public virtual Worker {
    
    ...};
class SingingWaiter: public Singer, public Waiter {
    
    ...};

If a class has an indirect virtual base, you must explicitly call one of the virtual base's constructors unless you only need to use the virtual base's default constructor.
C++ prohibits information from being automatically passed to the base class through an intermediate class when the base class is virtual. However, the compiler must construct the base class object components before constructing the derived object; in the above case, the compiler will use Worker's default constructor.
SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk,p), Singer(wk,v) {}
Multiple inheritance calls which method of the base class : This can be done in a modular way and using scope resolution operators to clarify the programmer's intent.

void Worker::Data() const
{
    
    
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}
void Waiter::Data() const
{
    
    
    cout << "Panache rating: " << panache << "\n";
}
void Singer::Data() const
{
    
    
    cout << "Vocal range: " << pv[voice] << "\n";
}
void SingingWaiter::Data() const
{
    
    
    Singer::Data();
    Waiter::Data();
}
void SingingWaiter::Show() const
{
    
    
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}

Final routine :
workermi.h


#ifndef _PRIMERPLUS_WORKERMI_H
#define _PRIMERPLUS_WORKERMI_H
#include <cstring>
using namespace std;

class Worker   // an abstract base class
{
    
    
private:
    std::string fullname;
    long id;
protected:
    virtual void Data() const;
    virtual void Get();
public:
    Worker() : fullname("no one"), id(0L) {
    
    }
    Worker(const std::string & s, long n)
            : fullname(s), id(n) {
    
    }
    virtual ~Worker() = 0; // pure virtual function
    virtual void Set() = 0;
    virtual void Show() const = 0;
};

class Waiter : virtual public Worker
{
    
    
private:
    int panache;
protected:
    void Data() const;
    void Get();
public:
    Waiter() : Worker(), panache(0) {
    
    }
    Waiter(const std::string & s, long n, int p = 0)
            : Worker(s, n), panache(p) {
    
    }
    Waiter(const Worker & wk, int p = 0)
            : Worker(wk), panache(p) {
    
    }
    void Set();
    void Show() const;
};

class Singer : virtual public Worker
{
    
    
protected:
    enum {
    
    other, alto, contralto, soprano,
        bass, baritone, tenor};
    enum {
    
    Vtypes = 7};
    void Data() const;
    void Get();
private:
    static char *pv[Vtypes];    // string equivs of voice types
    int voice;
public:
    Singer() : Worker(), voice(other) {
    
    }
    Singer(const std::string & s, long n, int v = other)
            : Worker(s, n), voice(v) {
    
    }
    Singer(const Worker & wk, int v = other)
            : Worker(wk), voice(v) {
    
    }
    void Set();
    void Show() const;
};

// multiple inheritance
class SingingWaiter : public Singer, public Waiter
{
    
    
protected:
    void Data() const;
    void Get();
public:
    SingingWaiter()  {
    
    }
    SingingWaiter(const std::string & s, long n, int p = 0,
                  int v = other)
            : Worker(s,n), Waiter(s, n, p), Singer(s, n, v) {
    
    }
    SingingWaiter(const Worker & wk, int p = 0, int v = other)
            : Worker(wk), Waiter(wk,p), Singer(wk,v) {
    
    }
    SingingWaiter(const Waiter & wt, int v = other)
            : Worker(wt),Waiter(wt), Singer(wt,v) {
    
    }
    SingingWaiter(const Singer & wt, int p = 0)
            : Worker(wt),Waiter(wt,p), Singer(wt) {
    
    }
    void Set();
    void Show() const;
};

#endif

workermi.cpp

// workermi.cpp -- working class methods with MI
#include <iostream>
#include "workermi.h"
using namespace std;

// Worker methods
Worker::~Worker() {
    
     }

// protected methods
void Worker::Data() const
{
    
    
    cout << "Name: " << fullname << endl;
    cout << "Employee ID: " << id << endl;
}

void Worker::Get()
{
    
    
    getline(cin, fullname);
    cout << "Enter worker's ID: ";
    cin >> id;
    while (cin.get() != '\n')
        continue;
}

// Waiter methods
void Waiter::Set()
{
    
    
    cout << "Enter waiter's name: ";
    Worker::Get();
    Get();
}

void Waiter::Show() const
{
    
    
    cout << "Category: waiter\n";
    Worker::Data();
    Data();
}

// protected methods
void Waiter::Data() const
{
    
    
    cout << "Panache rating: " << panache << endl;
}

void Waiter::Get()
{
    
    
    cout << "Enter waiter's panache rating: ";
    cin >> panache;
    while (cin.get() != '\n')
        continue;
}

// Singer methods

char * Singer::pv[Singer::Vtypes] = {
    
    "other", "alto", "contralto",
                                     "soprano", "bass", "baritone", "tenor"};

void Singer::Set()
{
    
    
    cout << "Enter singer's name: ";
    Worker::Get();
    Get();
}

void Singer::Show() const
{
    
    
    cout << "Category: singer\n";
    Worker::Data();
    Data();
}

// protected methods
void Singer::Data() const
{
    
    
    cout << "Vocal range: " << pv[voice] << endl;
}

void Singer::Get()
{
    
    
    cout << "Enter number for singer's vocal range:\n";
    int i;
    for (i = 0; i < Vtypes; i++)
    {
    
    
        cout << i << ": " << pv[i] << "   ";
        if ( i % 4 == 3)
            cout << endl;
    }
    if (i % 4 != 0)
        cout << '\n';
    while (cin >>  voice && (voice < 0 || voice >= Vtypes) )
        cout << "Please enter a value >= 0 and < " << Vtypes << endl;
    while (cin.get() != '\n')
        continue;
}

// SingingWaiter methods
void SingingWaiter::Data() const
{
    
    
    Singer::Data();
    Waiter::Data();
}

void SingingWaiter::Get()
{
    
    
    Waiter::Get();
    Singer::Get();
}

void SingingWaiter::Set()
{
    
    
    cout << "Enter singing waiter's name: ";
    Worker::Get();
    Get();
}

void SingingWaiter::Show() const
{
    
    
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}

workmi.cpp

// workmi.cpp -- multiple inheritance
// compile with workermi.cpp
#include <iostream>
#include <cstring>
#include "workermi.h"
const int SIZE = 5;

int main()
{
    
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::strchr;

    Worker * lolas[SIZE];

    int ct;
    for (ct = 0; ct < SIZE; ct++)
    {
    
    
        char choice;
        cout << "Enter the employee category:\n"
             << "w: waiter  s: singer  "
             << "t: singing waiter  q: quit\n";
        cin >> choice;
        while (strchr("wstq", choice) == NULL)
        {
    
    
            cout << "Please enter a w, s, t, or q: ";
            cin >> choice;
        }
        if (choice == 'q')
            break;
        switch(choice)
        {
    
    
            case 'w':   lolas[ct] = new Waiter;
                break;
            case 's':   lolas[ct] = new Singer;
                break;
            case 't':   lolas[ct] = new SingingWaiter;
                break;
        }
        cin.get();
        lolas[ct]->Set();
    }

    cout << "\nHere is your staff:\n";
    int i;
    for (i = 0; i < ct; i++)
    {
    
    
        cout << endl;
        lolas[i]->Show();
    }
    for (i = 0; i < ct; i++)
        delete lolas[i];
    cout << "Bye.\n";
    // cin.get();
    // cin.get();
    return 0;
}

Mixing virtual and non-virtual base classes :

  1. If the base class is a virtual base class, the derived class will contain a subobject of the base class;
  2. If the base class is not a virtual base class, the derived class will contain multiple subobjects.

Using virtual base classes will change the way C++ resolves ambiguities :

  1. If a class inherits two or more members (data or methods) with the same name from different classes, using that member name without qualifying it with the class name will result in ambiguity.
  2. Doing so is not necessarily ambiguous if you are using a virtual base class. In this case, if a name dominates (dominates) all others, it can be used without qualifiers without causing ambiguity.
  3. How does one member name take precedence over another? A name in a derived class takes precedence over the same name in a direct or indirect ancestor class.

MI Summary

MI without using virtual base classes:

  1. If a class inherits two members with the same name from two different classes, you need to use a class qualifier in the derived class to distinguish them.
  2. If a class inherits a non-virtual base class through multiple ways, the class inherits an instance of the non-virtual base class from each way.

MI using a virtual base class:

  1. A base class becomes a virtual base class when a derived class uses the keyword virtual to indicate derivation.
  2. A class derived from one or more instances of a virtual base class will inherit only one base class object.
  3. A derived class with an indirect virtual base class contains a constructor that directly calls the constructor of the indirect base class, which is illegal for an indirect non-virtual base class;
  4. Name ambiguity is resolved through precedence rules.

14.4 Class Templates

  1. Each function header will start with the same template declaration template <class Type>// type generic identifier, called a type parameter
  2. Also change the class qualifier from Stack:: toStack<Type>::
template <class Type> 		// or template <typename Type>
bool Stack<Type>::push(const Type & item)
{
    
    
    ...
}

// 类模板实例化
Stack<int> kernels; 		// create a stack of ints
Stack<string> colonels; 	// create a stack of string objects
  1. Class templates and member function templates are not class and member function definitions, they are C++ compiler directives that tell how to generate class and member function definitions.
  2. Template member functions cannot be placed in separate implementation files.
  3. Since templates are not functions, they cannot be compiled individually. Templates must be used with a specific template instantiation request. The easiest way to do this is to put all template information in a header file and include that header file in the files that will use those templates.
  4. Merely including a template in a program does not generate a template class, but must request instantiation.
  5. An object of type template class needs to be declared by substituting the generic name with the desired concrete type.

Example: A Class Template Routine Popped and Pushed

stacktp.h

// stacktp.h -- a stack template
#ifndef PRIMERPLUS_SATCKTP_H
#define PRIMERPLUS_SATCKTP_H

template <class Type>
class Stack // 什么时候对类实例化,什么时候Type变成需要的
{
    
    
private:
    enum {
    
    MAX = 10};    // constant specific to class
    Type items[MAX];    // holds stack items
    int top;            // index for top stack item
public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type & item); // add item to stack
    bool pop(Type & item);        // pop top into item
};

template <class Type>
Stack<Type>::Stack()
{
    
    
    top = 0;
}

template <class Type>
bool Stack<Type>::isempty()
{
    
    
    return top == 0;
}

template <class Type>
bool Stack<Type>::isfull()
{
    
    
    return top == MAX;
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    
    
    if (top < MAX)
    {
    
    
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    
    
    if (top > 0)
    {
    
    
        item = items[--top];
        return true;
    }
    else
        return false;
}

#endif //PRIMERPLUS_STCKTP_H

stacktem.cpp

// stacktem.cpp -- testing the template stack class
#include <iostream>
#include <string>
#include <cctype>
#include "stacktp.h"
using std::cin;
using std::cout;

int main()
{
    
    
    Stack<std::string> st;   // create an empty stack

    char ch;
    std::string po;

    cout << "Please enter A to add a purchase order,\n"
         << "P to process a PO, or Q to quit.\n";
    while (cin >> ch && std::toupper(ch) != 'Q')
    {
    
    
        while (cin.get() != '\n')
            continue;
        if (!std::isalpha(ch))
        {
    
    
            cout << '\a';
            continue;
        }
        switch(ch)
        {
    
    
            case 'A':
            case 'a': cout << "Enter a PO number to add: ";
                cin >> po;
                if (st.isfull())
                    cout << "stack already full\n";
                else
                    st.push(po);
                break;
            case 'P':
            case 'p': if (st.isempty())
                    cout << "stack already empty\n";
                else {
    
    
                    st.pop(po);
                    cout << "PO #" << po << " popped\n";
                    break;
                }
        }
        cout << "Please enter A to add a purchase order,\n"
             << "P to process a PO, or Q to quit.\n";
    }
    cout << "Bye\n";
    // cin.get();
    // cin.get();
    return 0;
}

Example: A simple array template that allows specifying the size of the array

  1. One approach is to use a dynamic array and constructor parameter in the class to provide the number of elements.
  2. Another way is to use a template parameter (expression parameter) to provide the size of a regular array, which is what C++11's new template array does.
  3. Expression parameters can be integers, enumerations, references, or pointers. If it isdouble m illegal
  4. Template code cannot modify the value of a parameter, nor can it use the address of a parameter. such as n++, &n
  5. When instantiating a template, the value used as an expression parameter must be a constant expression.
template <class T, int n>   // T为类型参数,n为int类型(非类型/表达式参数)
class ArrayTP
{
    
    
...
};
// 定义一个名为ArrayTP<double, 12>的类,并创建一个类型为ArrayTP<double, 12>的eggweight对象。
ArrayTP<double, 12> eggweights; 

Difference between constructor method and expression parameter method:

  1. Constructor methods use heap memory managed by new and delete, while expression parameter methods use the memory stack maintained for automatic variables.
  2. Advantage of expression parameters: Execution will be faster, especially if many small arrays are used.
  3. Disadvantage of expression parameters: each array size will generate its own template.
  4. The constructor method is more general because the array size is stored in the definition as a class member (rather than hardcoded). This makes it possible to assign arrays of one size to arrays of another size, and to create classes that allow arrays to vary in size.
// 生成两个独立的类声明
ArrayTP<double, 12> eggweights;
ArrayTP<double, 13> donuts;
// 只生成一个类声明,并将数组大小信息传递给类的构造函数
Stack<int> eggs(12);
Stack<int> dunkers(13);

arraytp.h

//arraytp.h  -- Array Template

#ifndef PRIMERPLUS_ARRAYTP_H
#define PRIMERPLUS_ARRAYTP_H

#include <iostream>
#include <cstdlib>

template <class T, int n>   // T为类型参数,n为int类型(非类型/表达式参数)
class ArrayTP
{
    
    
private:
    T ar[n];
public:
    ArrayTP() {
    
    };
    explicit ArrayTP(const T & v);
    virtual T & operator[](int i);
    virtual T operator[](int i) const;
};

template <class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v)
{
    
    
    for (int i = 0; i < n; i++)
        ar[i] = v;
}

template <class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
    
    
    if (i < 0 || i >= n)
    {
    
    
        std::cerr << "Error in array limits: " << i
                  << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

template <class T, int n>
T ArrayTP<T,n>::operator[](int i) const
{
    
    
    if (i < 0 || i >= n)
    {
    
    
        std::cerr << "Error in array limits: " << i
                  << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

#endif //PRIMERPLUS_ARRAYTP_H

Template Versatility

Template classes can be used as base classes, as component classes, and as type parameters for other templates.

template <typename T> // or <class T>
class Array
{
    
    
private:
    T entry;
    ...
};

template <typename Type>
class GrowArray : public Array<Type> {
    
    ...}; // inheritance

template <typename Tp>
class Stack
{
    
    
    Array<Tp> ar; // use an Array<> as a component
    ...
};
...
Array < Stack<int> > asi; // an array of stacks of int
// 上面一行代码C++98要求>>之间有空格,C++11不要求

1. Use templates recursively

In the template syntax, the order of the dimensions is reversed from the equivalent two-dimensional array.

// 以下两条命令等价
ArrayTP< ArrayTP<int,5>, 10> twodee;
int twodee[10][5];

twod.cpp

// twod.cpp -- making a 2-d array
#include <iostream>
#include "arraytp.h"

int main(void)
{
    
    
    using std::cout;
    using std::endl;
    ArrayTP<int, 10> sums;
    ArrayTP<double, 10> aves;
    ArrayTP< ArrayTP<int,5>, 10> twodee;

    int i, j;

    for (i = 0; i < 10; i++)
    {
    
    
        sums[i] = 0;
        for (j = 0; j < 5; j++)
        {
    
    
            twodee[i][j] = (i + 1) * (j + 1);
            sums[i] += twodee[i][j];
        }
        aves[i] = (double) sums[i] / 10;
    }

    for (i = 0; i < 10; i++)
    {
    
    
        for (j = 0; j < 5; j++)
        {
    
    
            cout.width(2);
            cout << twodee[i][j] << ' ';
        }
        cout << ": sum = ";
        cout.width(3);
        cout  << sums[i] << ", average = " << aves[i] << endl;
    }

    cout << "Done.\n";
    // std::cin.get();
    return 0;
}

out:

 1  2  3  4  5 : sum =  15, average = 1.5
 2  4  6  8 10 : sum =  30, average = 3
 3  6  9 12 15 : sum =  45, average = 4.5
 4  8 12 16 20 : sum =  60, average = 6
 5 10 15 20 25 : sum =  75, average = 7.5
 6 12 18 24 30 : sum =  90, average = 9
 7 14 21 28 35 : sum = 105, average = 10.5
 8 16 24 32 40 : sum = 120, average = 12
 9 18 27 36 45 : sum = 135, average = 13.5
10 20 30 40 50 : sum = 150, average = 15
Done.

2. Use multiple type parameters

Templates can contain multiple parameter types. Assuming you want your class to hold two values, you can create and use a Pair template to hold two different values ​​(the Standard Template Library provides a similar template called pair).

pairs.cpp

// 方法first( ) const和second( ) const报告存储的值,
// 由于这两个方法返回Pair数据成员的引用,因此让您能够通过赋值重新设置存储的值。
// pairs.cpp -- defining and using a Pair template
#include <iostream>
#include <string>
template <class T1, class T2>
class Pair
{
    
    
private:
    T1 a;
    T2 b;
public:
    T1 & first();
    T2 & second();
    T1 first() const {
    
     return a; }
    T2 second() const {
    
     return b; }
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) {
    
     }
    Pair() {
    
    }
};

template<class T1, class T2>
T1 & Pair<T1,T2>::first()
{
    
    
    return a;
}
template<class T1, class T2>
T2 & Pair<T1,T2>::second()
{
    
    
    return b;
}

int main()
{
    
    
    using std::cout;
    using std::endl;
    using std::string;
    Pair<string, int> ratings[4] =
            {
    
    
                    Pair<string, int>("The Purpled Duck", 5),
                    Pair<string, int>("Jaquie's Frisco Al Fresco", 4),
                    Pair<string, int>("Cafe Souffle", 5),
                    Pair<string, int>("Bertie's Eats", 3)
            };

    int joints = sizeof(ratings) / sizeof (Pair<string, int>);
    cout << "Rating:\t Eatery\n";
    for (int i = 0; i < joints; i++)
        cout << ratings[i].second() << ":\t "
             << ratings[i].first() << endl;
    cout << "Oops! Revised rating:\n";
    ratings[3].first() = "Bertie's Fab Eats";
    ratings[3].second() = 6;
    cout << ratings[3].second() << ":\t "
         << ratings[3].first() << endl;
    // std::cin.get();
    return 0;
}

out:

Rating:  Eatery
5:       The Purpled Duck
4:       Jaquie's Frisco Al Fresco
5:       Cafe Souffle
3:       Bertie's Eats
Oops! Revised rating:
6:       Bertie's Fab Eats

3. Default type template parameters

template <class T1, class T2 = int> class Topo {
    
    ...};
Topo<double, double> m1; 	// T1 is double, T2 is double
Topo<double> m2; 			// T1 is double, T2 is int

While you can provide default values ​​for class template type parameters, you cannot provide default values ​​for function template parameters. However, default values ​​can be provided for non-type parameters, and this is true for both class and function templates.

Reification of the template

Templates describe classes generically, while reification generates class declarations with concrete types.
In layman's terms: the template class does not generate the definition of the class, it just talks about the following shelf of the class. Templates are the way to generate classes. Reification is the filling of a template with a specific type.

1. Implicit instantiation (prepare when used)

One or more objects are declared, indicating the required types, and the compiler generates concrete class definitions using the recipe provided by the generic template.
The compiler does not generate an implicit instantiation of a class until the object is needed:

ArrayTP<int, 100> stuff;		// 隐式实例化

ArrayTP<double, 30> * pt;		// 此时还没有使用创建对象,没有分配内存,只是创建了一个指针指向这样的对象
pt = new ArrayTP<double, 30>;	// 该语句导致编译器生成类定义,并根据该定义创建一个对象。

2. Explicit instantiation (prepare when useless)

template class ArrayTP<string, 100>; _// generate ArrayTP<string, 100> class_
When a class is declared using the keyword template and indicating the desired type, the compiler will generate an explicit instantiation of the class declaration.
The compiler will also generate class declarations (including method definitions), although no class objects are created or mentioned.
Faster, saving the time to create this class during the runtime.

3. Display materialization (all special treatment)

template <> class Classname<specialized-type-name> { ... };
There are already class definitions, modify some of them, and re-implement the class.
Sometimes it may be necessary to modify a template to behave differently when instantiated for a particular type.

4. Partial concretization (partial special treatment)

The <> after the keyword template declares an unreified type parameter.

Example: A Template Reified Routine

#include <iostream>
using namespace std;

template<class T1, class T2>
class A
{
    
    
public:
    void show();
};

template<class T1, class T2>
void A<T1, T2>::show()
{
    
    
    cout << "this is a general definition." << endl;
}

// 显式实例化,A<double, double>这个类在内存中已经存在
template class A<double, double>;

// 显示具体化,不再使用通用的方法,所有类型都指定为某一特定的类型
template<>
class A<int, int>
{
    
    
public:
    void show();
};
// template<>   不用这一行语句
void A<int, int>::show()
{
    
    
    cout << "explicit specialization." << endl;
}

// 部分具体化
template<class T1>  // <>中放没有具体化的,T1泛型,T2指定为某种特殊的类型
class A<T1, int>
{
    
    
public:
    void show();
};
template<class T1>  // 注意这里
void A<T1, int>::show()
{
    
    
    cout << "partial specialization." << endl;
}

int main(void)
{
    
    
    A<char, char> a1;   // 隐式实例化
    a1.show();
    A<int, int> a2;     // 显示具体化过
    a2.show();
    A<double, int> a3;  // 调用部分具体化
    a3.show();

    return 0;
}

Example: template class and template function as member variables

Templates can be used as members of structs, classes, or template classes. To fully implement the design of the STL, this feature must be used.

// 模板类和模板函数做成员变量
#include <iostream>
using namespace std;

template<class T>
class beta
{
    
    
private:
    template<class V>   // beta模板类里面的成员变量是一个hold模板类
            class hold
            {
    
    
            private:
                V val;
            public:
                hold(V v=0):val(v) {
    
    }   // 内联函数,构造函数
                void show() const {
    
    cout << val << endl;}
                V Value() const {
    
    return val;}
            };
    hold<T> q;
    hold<int> n;    // 基于int类型的hold对象
public:
    beta(T t, int i):q(t), n(i) {
    
    }
    void Show() const {
    
     q.show(); n.show(); }
    template<class U>   // 定义模板函数
            U blab(U u, T t) {
    
    return (q.Value() + n.Value()) * u / t;}
            // 注意q.Value()后面加括号,因为调用的是一个函数。
};

int main(void)
{
    
    
    beta<double> guy(1.2, 3);
    guy.Show();
    cout << guy.blab(10, 6.1) << endl;  // (f+int)*int/f,返回int类型
    cout << guy.blab(10.0, 6.1) << endl;// (f+int)*f/f,返回f类型

    return 0;
}

Example: Template class as parameter

Templates can contain type parameters (such as typename T ) and non-type parameters (such as int n ). Templates can also contain parameters that are themselves templates, which are new features of templates used to implement STL.

template <typename T>
class King {
    
    ...};

template <template <typename T> class Thing>
class Crab{
    
    ...};

Crab<King> legs;
// 模板类做参数
#include <iostream>
#include "stacktp.h"
using namespace std;

template <template <class T> class Thing>   // 参数Thing是一个模板类型
        class Crab
        {
    
    
        private:
            Thing<int> s1;      // Ting<int>类的对象
            Thing<double> s2;   // Ting<double>类的对象
        public:
            Crab() {
    
    }
            bool push(int a, double x) {
    
    return s1.push(a) && s2.push(x);}
            bool pop(int & a, double & x) {
    
    return s1.pop(a) && s2.pop(x);}
        };

int main(void)
{
    
    
    Crab<Stack> nebula;    // 包含两个对象Stack<int>和Stack<double>,包含两个栈
    int ni;
    double nb;
    cout << "Enter int double pairs, such as 4 3.5 (0 to end):\n";
    while (cin >> ni && ni > 0 && cin >> nb && nb > 0)
    {
    
    
        if (!nebula.push(ni, nb))
            break;
    }
    while (nebula.pop(ni, nb))
        cout << ni << ", " << nb << endl;
    cout << "Done.\n";

    return 0;
}

Non-template friend ; declares a regular function as a friend function.
Friend functions can access global objects; can use global pointers to access non-global objects; can create their own objects; can access static data members of template classes independent of objects.

// 模板类中使用非模板友元函数
template <class T>
class HasFriend
{
    
    
public:
    friend void counts(); // friend to all HasFriend instantiations
    friend void report(HasFriend<T> &); // 要提供模板类参数,必须指明具体化。
    ...
};

// 友元声明的格式如下:
// 带HasFriend<int>参数的report( )将成为HasFriend<int>类的友元。
class HasFriend<int>
{
    
    
    friend void report(HasFriend<int> &); // bound template friend
    ...
};

// 友元函数的定义:
// report( )本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显式具体化:
void report(HasFriend<int> & hf) {
    
    ...};    // explicit specialization for int
void report(HasFriend<double> & hf) {
    
    ...};
// 模板类中使用非模板友元函数
#include <iostream>
using namespace std;
template <typename T>
class HasFriend
{
    
    
private:
    T item;
    static int ct;    // HasFriend<int> a1, a2; a1和a2公用一个ct变量
public:
    HasFriend(const T & i):item(i) {
    
    ct++;}
    ~HasFriend() {
    
    ct--;}
    friend void counts();                   // display ct
    friend void reports(HasFriend<T> &);    // display item
};

template <typename T>   // 这里要注意
int HasFriend<T>::ct = 0;

void counts()
{
    
    
    cout << "int count: " << HasFriend<int>::ct << ";";
    cout << " double count: " << HasFriend<double>::ct << ".\n";
}

void reports(HasFriend<int> & hf)
{
    
    
    cout << "HasFriend<int>: " << hf.item << endl;
}

void reports(HasFriend<double> & hf)
{
    
    
    cout << "HasFriend<double>: " << hf.item << endl;
}

int main(void)
{
    
    
    counts();
    HasFriend<int> hfi1(10);
    counts();
    HasFriend<int> hfi2(20);
    counts();
    HasFriend<double> hfi3(30.5);
    counts();
    reports(hfi1);
    reports(hfi2);
    reports(hfi3);

    return 0;
}

Constrained and unconstrained template friend functions

Constrained (bound) template friends , that is, the type of the friend depends on the type when the class is instantiated;
the bound template friend function is the incarnation of the template declared outside the class.

// 首先,在类定义的前面声明每个模板函数。
template <typename T> void counts();
template <typename T> void report(T &);

// 然后,在函数中再次将模板声明为友元。
template <typename TT>
class HasFriendT
{
    
    
    ...
    friend void counts<TT>();   // counts( )函数没有参数,因此必须使用模板参数语法(<TT>)来指明其具体化。
    friend void report<>(HasFriendT<TT> &); // report<>可以为空,可以从函数参数推断出模板类型参数
};

// 然后,函数定义
template <typename T>
void counts() {
    
    ...}

template <typename T>
void report(T & hf) {
    
    ...}

// 最后,在main()中调用
counts<int>();

An unbound template friend , that is, all incarnations of the friend are friends of every incarnation of the class.
By declaring a template inside a class, unconstrained friend functions can be created, i.e. every function incarnation is a friend of every class incarnation.
Friend template type parameters are different from template class type parameters.

// 非约束模板友元
// manyfrnd.cpp -- unbound template friend to a template class
#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class ManyFriend
{
    
    
private:
    T item;
public:
    ManyFriend(const T & i) : item(i) {
    
    }
    template <typename C, typename D> friend void show2(C &, D &);
    // 在类内部声明,非约束友元,友元模板类型参数与模板类类型参数是不同的
};

template <typename C, typename D> void show2(C & c, D & d)
{
    
    
    cout << c.item << ", " << d.item << endl;
}

int main()
{
    
    
    ManyFriend<int> hfi1(10);
    ManyFriend<int> hfi2(20);
    ManyFriend<double> hfdb(10.5);
    cout << "hfi1, hfi2: ";
    show2(hfi1, hfi2);  // hfi1, hfi2: 10, 20
    // 具体化是void show2<ManyFriend<int> &, ManyFriend<int> &>(ManyFriend<int> & c, ManyFriend<int> & d);
    cout << "hfdb, hfi2: ";
    show2(hfdb, hfi2);  // hfdb, hfi2: 10.5, 20
    // std::cin.get();
    return 0;
}

Template aliases (C++11)

// 可使用typedef为模板具体化指定别名:
typedef std::array<std::string, 12> arrst;
arrst months; // months is type std::array<std::string, 12>

// 使用模板提供一系列别名:
template<typename T>
using arrtype = std::array<T,12>; 	// template to create multiple aliases
arrtype<double> gallons; 			// gallons is type std::array<double, 12>
                                	// arrtype<T>表示类型std::array<T, 12>

// C++11允许将语法using =用于非模板。用于非模板时,这种语法与常规typedef等价:
typedef const char * pc1;       // typedef syntax
using pc2 = const char *;       // using = syntax
typedef const int *(*pa1)[10];  // typedef syntax
using pa2 = const int *(*)[10]; // using = syntax

14.5 Summary

  1. C++ provides several means of reusing code. Public inheritance, introduced in Chapter 13, establishes an is-a relationship so that derived classes can reuse code from base classes. Private inheritance and protected inheritance also enable the reuse of base class code, but establish a has-a relationship. When using private inheritance, the public and protected members of the base class will become private members of the derived class; when using protected inheritance, the public and protected members of the base class will become protected members of the derived class. No matter which kind of inheritance is used, the public interface of the base class becomes the internal interface of the derived class. This is sometimes called inheriting implementation, but not inheriting interfaces, because derived class objects cannot explicitly use base class interfaces. Therefore, a derived object cannot be considered a kind of base class object. For this reason, a base class pointer or reference cannot point to a derived class object without an explicit type conversion.
  2. You can also reuse class code by developing classes that contain object members. This approach is known as containment, hierarchy, or composition, and it also establishes has-a relationships. Containment is easier to implement and use than private and protected inheritance, so it is usually preferred. However, private inheritance and protected inheritance have some different functionality than containment. For example, inheritance allows derived classes to access protected members of the base class; it also allows derived classes to redefine virtual functions inherited from the base class. Because inclusion is not inheritance, these features cannot be used when class code is reused through inclusion. On the other hand, if you need to use several objects of a certain class, using contains is more suitable. For example, a State class could contain an array of County objects.
  3. Multiple inheritance (MI) enables the reuse of code from multiple classes in class design. A private MI or protected MI establishes a has-a relationship, while a public MI establishes an is-a relationship. MI will bring some problems, that is, define the same name multiple times and inherit multiple base class objects. You can use class qualifiers to solve the problem of name ambiguity, and use virtual base classes to avoid the problem of inheriting multiple base class objects. But after using the virtual base class, it is necessary to introduce new rules for writing the constructor initialization list and solving the ambiguity problem.
  4. Class templates enable the creation of generic class designs in which types (usually member types) are represented by type parameters. A typical template is as follows:
template <class T>
class Ic
{
    
    
    T v;
    ...
public:
    Ic(const T & val) : v(val) {
    
     }
    ...
};

where T is the type parameter, used as a placeholder for the actual type to be specified later (this parameter can be any valid C++ name, but T and Type are usually used). In this environment, typename can also be used instead of class:

template <typename T> // same as template <class T>
class Rev {
    
    ...} ;
  1. A class definition (instantiation) is generated when a class object is declared and a specific type is specified. For example, the following declaration causes the compiler to generate a class declaration that replaces all type parameters T in the template with the actual type short in the declaration:
    class Ic<short> sic; _// implicit instantiation_
    Here, the class name is Ic, not Ic. Ic is called template materialization. Specifically, this is an implicit instantiation.
    Explicit instantiation occurs when a specific reification of a class is declared using the keyword template:
    template class IC<int>; _// explicit instantiation_
    in this case, the compiler will use the generic template to generate an int reification -- Ic<int>, although no object of this class has yet been requested.

Explicit reifications can be provided—concrete class declarations that override template definitions. A method begins with template<>, followed by the template class name, followed by angle brackets (which enclose the type to be reified). For example, the code to provide a dedicated Ic class for character pointers is as follows:

template <> class Ic<char *>.
{
    
    
    char * str;
    ...
public:
    Ic(const char * s) : str(s) {
    
     }
    ...
};

This way, a declaration like the following will use the chic specific definition instead of the generic template:
class Ic<char *> chic;

  1. Class templates can specify multiple generic types, and can also have non-type parameters:
template <class T, class TT, int n>
class Pals {
    
    ...};

The following declaration will generate an implicit instantiation, substituting double for T, string for TT, and 6 for n:
Pals<double, string, 6> mix;

  1. Class templates can also contain parameters that are themselves templates:
template < template <typename T> class CL, typename U, int z>
class Trophy {
    
    ...};

Where z is an int value, U is the type name, and CL is a class template declared using template<typename, T>.

  1. Class templates can be partially reified:
    the first declaration creates a reification for the case where the two types are the same and n has a value of 6.
    Likewise, the second declaration creates a reification for the case where n is equal to 100; the
    third declaration creates a reification for the case where the second type is a pointer to the first type.
template <class T> Pals<T, T, 10> {
    
    ...};
template <class T, class TT> Pals<T, TT, 100> {
    
    ...};
template <class T, int n> Pals <T, T*, n> {
    
    ...};
  1. Template classes can be used as members of other classes, structs, and templates.
  2. The purpose of all these mechanisms is to enable programmers to reuse tested code without duplicating it by hand. This simplifies programming work and provides program reliability.

Guess you like

Origin blog.csdn.net/qq_39751352/article/details/127410037