The way of C/C + + Learning: polymorphism

Posted by Zaynt on Wed, 22 Dec 2021 15:37:57 +0100

The way of C/C + + Learning: polymorphism

catalogue

  1. Basic concepts of polymorphism
  2. Upward type conversion and problems
  3. How to implement dynamic binding
  4. Abstract base classes and pure virtual functions
  5. Pure virtual functions and multiple inheritance
  6. virtual destructor
  7. Override, overload, redefine

1. Basic concepts of polymorphism

  1. Polymorphism is the third basic feature besides data abstraction and inheritance in object-oriented programming language.
  2. Polymorphism provides another layer of isolation between interfaces and concrete implementations.
  3. Polymorphism not only improves the readability and organization of the code, but also makes the created program extensible. The project can be extended not only in the initial creation period, but also when the project needs new functions.
  4. c + + supports compile time polymorphism (static polymorphism) and run-time polymorphism (dynamic polymorphism). Operator overloading and function overloading are compile time polymorphism, while derived classes and virtual functions implement run-time polymorphism.
  5. The difference between static polymorphism and dynamic polymorphism is whether the function address is bound early (static binding) or late (dynamic binding).
    1. If a function is called, the call address of the function can be determined at the compilation stage and generate code, which is static polymorphism (compile time polymorphism), that is, the address is bound early.
    2. If the call address of a function cannot be compiled, cannot be determined during compilation, and needs to be determined at runtime, it belongs to late binding (dynamic polymorphism, runtime polymorphism).
//Calculator
class Caculator {
public:
    void setA(int a) {
        this->mA = a;
    }

    void setB(int b) {
        this->mB = b;
    }

    void setOperator(string oper) {
        this->mOperator = oper;
    }

    int getResult() {

        if (this->mOperator == "+") {
            return mA + mB;
        } else if (this->mOperator == "-") {
            return mA - mB;
        } else if (this->mOperator == "*") {
            return mA * mB;
        } else if (this->mOperator == "/") {
            return mA / mB;
        }
    }

private:
    int mA;
    int mB;
    string mOperator;
};

//This program is not conducive to expansion and maintenance. If the function is modified or extended, it needs to be modified on the basis of source code
//A basic principle of object-oriented programming: opening and closing principle (closed for modification and open for extension)

//Abstract base class
class AbstractCaculator {
public:
    void setA(int a) {
        this->mA = a;
    }

    virtual void setB(int b) {
        this->mB = b;
    }

    virtual int getResult() = 0;

protected:
    int mA;
    int mB;
};

//Addition calculator
class PlusCaculator : public AbstractCaculator {
public:
    virtual int getResult() {
        return mA + mB;
    }
};

//Subtraction calculator
class MinusCaculator : public AbstractCaculator {
public:
    virtual int getResult() {
        return mA - mB;
    }
};

//Multiplication calculator
class MultipliesCaculator : public AbstractCaculator {
public:
    virtual int getResult() {
        return mA * mB;
    }
};

void DoBussiness(AbstractCaculator *caculator) {
    int a = 10;
    int b = 20;
    caculator->setA(a);
    caculator->setB(b);
    cout << "Calculation results:" << caculator->getResult() << endl;
    delete caculator;
}

2. Upward type conversion and problems

1. Problems

  1. Object can be used as its own class or as an object of its base class, and it can also be operated through the address of the base class.
  2. Taking the address (pointer or reference) of an object and treating it as the address of the base class is called up type conversion.
  3. The parent class reference or pointer can point to the child class object, and the child class object can be operated through the parent class pointer or reference.
class Animal {
public:
    void speak() {
        cout << "Animals are singing..." << endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        cout << "The dog is singing..." << endl;
    }
};

void DoBussiness(Animal &animal) {
    animal.speak();
}

void test3() {
    Dog dog;
    DoBussiness(dog);
}
  1. The object we pass in to dobussness is dog, not animal. The output result should be Dog::speak.

2. Problem solving (virtual function)

  1. Associating a function body with a function call is called binding
  2. When the binding is completed before the program runs (by the compiler and connector), it is called early binding. There is only one function call mode in C language, that is, early binding.
  3. The above problem is caused by early binding, because the compiler does not know the correct function to call when there is only an Animal address.
  4. Compilation selects function calls based on the type of pointer or reference to an object. At this time, because the parameter type of dobussness is animal &, the compiler determines that the speak that should be called is Animal::speak, rather than the real incoming object Dog::speak.
  5. The solution is late binding (late binding, dynamic binding, runtime binding, late binding), which means that binding occurs at runtime according to the actual type of the object.
  6. To implement this dynamic binding in C + + language, there must be some mechanism to determine the type of runtime object and call appropriate member functions.
  7. C + + dynamic polymorphism is realized through virtual functions. Virtual functions allow subclasses (derived classes) to redefine the member functions of the parent class (base class), while subclasses (derived classes) redefine the virtual functions of the parent class (base class), which is called override or override.
  8. For dynamic binding of a specific function, c + + requires that the virtual keyword be used when declaring the function in the base class. Dynamic binding also works on the virtual function
  9. To create a virtual member function that needs dynamic binding, you can simply add the virtual keyword in front of the function declaration, which is not required during definition
  10. If a function is declared virtual in the base class, it is virtual in all derived classes
  11. In a derived class, the redefinition of a virtual function is called override. The virtual keyword can only modify member functions, and the constructor cannot be a virtual function
  12. Note: you only need to declare a function in the base class as virtual Calling all derived class functions that match the declared behavior of the base class will use the virtual mechanism.
class Animal {
public:
    virtual void speak() {
        cout << "Animals are singing..." << endl;
    }
};

class Dog : public Animal {
public:
    virtual void speak() {
        cout << "The dog is singing..." << endl;
    }
};

void DoBussiness(Animal &animal) {
    animal.speak();
}

void test3() {
    Dog dog;
    DoBussiness(dog);
}

3. How to implement dynamic binding

  1. When the compiler finds that there are virtual functions in our class, the compiler will create a virtual function table, put the function entry address of the virtual function into the virtual function table, and secretly add a pointer in the class. This pointer is the virtual table pointer (vptr), which refers to the virtual function table of the object.
  2. During polymorphic calls, the virtual function table is found according to the vptr pointer to realize dynamic binding.
  3. In the compilation stage, the compiler secretly adds a vptr pointer, but the vptr pointer does not initialize to point to the virtual function table (vtable),
  4. When the object is built, that is, when the constructor is called when the object is initialized. First, the compiler will add some vptr pointer initialization code to each constructor we write by default.
  5. If the constructor is not provided, the compiler will provide the default constructor, so it will do this in the default constructor to initialize the vptr pointer to point to the virtual function table of the object.
  6. The subclass inherits the base class. The subclass inherits the vptr pointer of the base class, which points to the virtual function table of the base class. When the subclass calls the constructor, the vptr pointer of the subclass points to the virtual function table of the subclass.
  7. When the subclass does not override the virtual function of the base class:

	 Animal* animal = new Dog;
     animal->fun1();
     When the program is executed here, it will go animal Looking in the space pointed to vptr Pointer, through vptr Pointer found func1 At this time, because the subclass is not overridden, that is, it overrides the base class func1 Function, so call func1 When, the base class is still called func1.
  1. When subclasses override base class virtual functions:

     Animal* animal = new Dog;
     animal->fun1();
     When the program is executed here, it will go animal Looking in the space pointed to vptr Pointer, through vptr Pointer found func1 Function, because subclasses override the of the base class func1 Function, so call func1 When, the of the subclass is called func1.
  1. Conditions for establishment of polymorphism:
    1. Have inheritance
    2. The subclass overrides the virtual function of the parent class: the return value, function name and function parameters must be exactly the same as those of the parent class (except the destructor). The virtual keyword in the subclass can be written or not. It is recommended to write
    3. Type compatible, parent class pointer, parent class reference to child class object

4. Abstract base classes and pure virtual functions

  1. When designing, you often want the base class to be just an interface to its derived classes. That is to say, you only want to type up the base class and use its interface, and you don't want the user to actually create an object of the base class.

  2. At the same time, creating a pure virtual function allows the original member function to be placed in the interface without providing a piece of code that may be meaningless to the function.

  3. To do this, you can add at least one pure virtual function to the base class, so that the base class is called an abstract class

    1. Pure virtual functions use the keyword virtual followed by = 0. If you try to instantiate an abstract class, the compiler will prevent this operation.
    2. When inheriting an abstract class, all pure virtual functions must be implemented, otherwise the class derived from the abstract class is also an abstract class.
    3. Virtual void fun() = 0; Tell the compiler to reserve a location for the function in vtable, but do not put the address in this specific location.
  4. The purpose of establishing a public interface is to abstract the public operations of subclasses. A group of classes can be manipulated through a public interface, and a public class can be created without prior (or full implementation) of this public interface.

//Abstract making drinks
class AbstractDrinking{
public:
    //Boil water
    virtual void Boil() = 0;
    //Brew
    virtual void Brew() = 0;
    //Pour into a cup
    virtual void PourInCup() = 0;
    //Add excipients
    virtual void PutSomething() = 0;
    //Specified process
    void MakeDrink(){
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

//Making coffee
class Coffee : public AbstractDrinking{
public:
    //Boil water
    virtual void Boil(){
        cout << "Boiled farmer spring!" << endl;
    }
    //Brew
    virtual void Brew(){
        cout << "Brew coffee!" << endl;
    }
    //Pour into a cup
    virtual void PourInCup(){
        cout << "Pour the coffee into the cup!" << endl;
    }
    //Add excipients
    virtual void PutSomething(){
        cout << "Add milk!" << endl;
    }
};

//Making tea
class Tea : public AbstractDrinking{
public:
    //Boil water
    virtual void Boil(){
        cout << "Boiled tap water!" << endl;
    }
    //Brew
    virtual void Brew(){
        cout << "Brewing tea!" << endl;
    }
    //Pour into a cup
    virtual void PourInCup(){
        cout << "Pour the tea into the cup!" << endl;
    }
    //Add excipients
    virtual void PutSomething(){
        cout << "Add salt!" << endl;
    }
};

//Business function
void DoBussiness(AbstractDrinking* drink){
    drink->MakeDrink();
    delete drink;
}

void test(){
    DoBussiness(new Coffee);
    cout << "--------------" << endl;
    DoBussiness(new Tea);
}

5. Pure virtual function and multi inheritance

  1. Most object-oriented languages do not support multi inheritance, but most object-oriented languages support the concept of interface. There is no concept of interface in c + +, but the interface can be realized through pure virtual functions.
  2. In the interface class, there are only function prototype definitions and no data definitions.
  3. Multiple inheritance interfaces do not cause ambiguity and complexity problems. The interface class is only a function declaration, not a function implementation. The subclass needs to define the function implementation according to the function description.
  4. Note: all declarations except destructors are pure virtual functions.

6. Virtual destructor

1. Function of virtual destructor

  1. The virtual destructor is to solve the problem that the pointer of the base class points to the derived class object, and delete the derived class object with the pointer of the base class.
class People{
public:
    People(){
        cout << "Constructor People!" << endl;
    }
    virtual void showName() = 0;
    virtual ~People(){
        cout << "Destructor People!" << endl;
    }
};

class Worker : public People{
public:
    Worker(){
        cout << "Constructor Worker!" << endl;
        pName = new char[10];
    }
    virtual void showName(){
        cout << "Print the name of the subclass!" << endl;
    }
    ~Worker(){
        cout << "Destructor Worker!" << endl;
        if (pName != NULL){
            delete pName;
        }
    }
private:
    char* pName;
};

void test(){
    People* people = new Worker;
    people->~People();
}

2. Pure virtual destructor

  1. Pure virtual destructors are legal in c + +, but there is an additional limitation when using them: a function body must be provided for pure virtual destructors.
  2. So the question is: if you provide a function body for a virtual destructor, how can it be called a pure virtual destructor?
  3. The only difference between pure virtual destructors and non pure destructors is that pure virtual destructors make the base class an abstract class and cannot create objects of the base class.
  4. If the purpose of a class is not to achieve polymorphism and is used as a base class, do not declare a virtual destructor. On the contrary, declare a virtual destructor for the class.
//Impure virtual destructor
class A{
public:
    virtual ~A();
};

A::~A(){}

//Pure destructor
class B{
public:
    virtual ~B() = 0;
};

B::~B(){}

void test(){
    A a; //Class A is not an abstract class and can instantiate objects
    B b; //err, class B is an abstract class and cannot instantiate objects
}

7. Rewrite, overload, redefine

  1. Overload, function with the same name in the same scope
    1. Same scope
    2. The number of parameters, parameter order and parameter type are different
    3. It has nothing to do with the return value of the function
    4. Const can also be used as overload condition / / do (const teacher & T) {} do (teacher & T)
  2. Redefine (hide)
    1. Have inheritance
    2. A subclass (derived class) redefines a member of the parent class (base class) with the same name (non virtual function)
  3. Overwrite (overwrite)
    1. Have inheritance
    2. The subclass (derived class) overrides the virtual function of the parent class (base class)
    3. The function return value, function name and function parameters must be consistent with the virtual function in the base class

Topics: C C++