C++ polymorphism (detailed explanation)

1. The concept of polymorphism

1.1. The concept of polymorphism

Polymorphism: multiple forms. The specific point is to complete a certain behavior. When different objects complete it, different states will be produced.

For example: Take the act of buying a ticket. When ordinary people buy tickets, they buy them at full price; when students buy tickets, they buy them at half price; soldiers
buy tickets Priority is given to buying tickets.

1.2. Classification of polymorphism

Polymorphism is divided into static polymorphism and dynamic polymorphism

1) Static polymorphism, also known as static binding or early binding (early binding): function overloading and function templates instantiate multiple functions (essentially also function overloading). Static polymorphism is also called polymorphism during compilation. It is completed by the compiler during compilation. The compiler can infer which function is to be called based on the type of the function argument (implicit type conversion may be performed). If there is a corresponding The function calls the function, otherwise a compilation error occurs.

2) Dynamic polymorphism, also known as dynamic binding or late binding (late binding): During the running of the program, the specific behavior of the program is determined based on the specific type obtained, and specific functions are called, that is, runtime polymorphism . During program execution (not during compilation), the actual type of the referenced object is determined and the corresponding method is called based on its actual type.

  1. The parent class pointer or reference points to the parent class, and the virtual function of the parent class is called.
  2. The parent class pointer or reference points to the subclass, and the virtual function of the subclass is called.

2. Definition and implementation of polymorphism

2.1. Conditions for polymorphism

Polymorphism is when class objects with different inheritance relationships call the same function, resulting in different behaviors. For example, Student inherits
Person. Person objects buy tickets at full price, and Student objects buy tickets at half price.

There are two conditions for polymorphism in inheritance:

1. Virtual functions must be called through pointers or references of the base class

2. The called function must be a virtual function, and the derived class must override the virtual function of the base class

class Person //成人
{
  public:
  virtual void fun()
   {
       cout << "全价票" << endl; //成人票全价
   }
};
class Student : public Person //学生
{
   public:
   virtual void fun() //子类完成对父类虚函数的重写
   {
       cout << "半价票" << endl;//学生票半价
   }
};
void BuyTicket(Person* p)
{
   p->fun();
}

int main()
{
   Student st;
   Person p;
   BuyTicket(&st);//子类对象切片过去
   BuyTicket(&p);//父类对象传地址
}

2.2. Virtual function

Virtual function: A class member function modified by virtual is called a virtual function.

class Person {
public:
    virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

2.3. Rewriting of virtual functions

Rewriting (overwriting) of virtual functions:There is a virtual function in the derived class that is exactly the same as the base class (that is, the difference between the derived class virtual function and the base class virtual function a>
The return value type, function name, and parameter list are exactly the same)
, it is said that the virtual function of the subclass overrides the virtual function of the base class.

class Person {
public:
    virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
    virtual void BuyTicket() 
    {
        cout << "买票-半价" << endl; 
    }

/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*/
 /*void BuyTicket() { cout << "买票-半价" << endl; }*/

};
void Func(Person& p)
{ 
    p.BuyTicket(); 
}
int main()
{
    Person ps;
    Student st;
    Func(ps);
    Func(st);
    return 0;
}

Two special cases of virtual function duplication:

1. Covariance (the return value types of base class and derived class virtual functions are different)

When a derived class overrides a base class virtual function, the return value type is different from the base class virtual function. That is, when a base class virtual function returns a pointer or reference to a base class object, and a derived class virtual function returns a pointer or reference to a derived class object, it is called covariance.

class A{};
class B : public A {};
class Person {
public:
    virtual A* f() {return new A;}
};
class Student : public Person {
public:
    virtual B* f() {return new B;}
};

2. Rewriting of destructors (the names of destructors of base classes and derived classes are different)

If the destructor of the base class is a virtual function, then the destructor of the derived class only needs to be defined, regardless of whether the virtual keyword is added or not,
will be the same as the destructor of the base class Constitutes an override, although the base class and derived class destructor names are different. Although the function names are different,
seems to violate the rewriting rules. In fact, it is not. It can be understood that the compiler has made special changes to the name of the destructor.
Management, the name of the destructor after compilation is unified into destructor.

class Person {
public:
    virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
    virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
    delete p1;
    delete p2;
    return 0;
}

2.4, override and final

 final: Modifies a virtual function to indicate that the virtual function cannot be overridden.

class Car
{
public:
    virtual void Drive() final {}
};
class Benz :public Car
{
public:
    virtual void Drive() {cout << "Benz-舒适" << endl;}
};

override: Check whether the derived class virtual function overrides a virtual function of the base class. If it does not, a compilation error will be reported.

class Car{
public:
    virtual void Drive(){}
};
class Benz :public Car {
public:
    virtual void Drive() override 
    {
        cout << "Benz-舒适" << endl;
    }
};

2.5. Comparison of overloading, overwriting (rewriting), hiding (redefining)


3. Abstract class

3.1. Abstract concepts

Write =0 after the virtual function, then this function is a pure virtual function. A class that contains pure virtual functions is called an abstract class (also called an interface class). Abstract classes cannot instantiate objects. A derived class cannot instantiate an object after inheritance. Only by overriding pure virtual functions can the derived class instantiate an object. Pure virtual functions specify that derived classes must be rewritten. In addition, pure virtual functions also reflect interface inheritance.

class Car
{
public:
    virtual void Drive() = 0;
};
class Benz :public Car
{
public:
    virtual void Drive()
    {
        cout << "Benz-舒适" << endl;
    }
};
class BMW :public Car
{
public:
    virtual void Drive()
    {
        cout << "BMW-操控" << endl;
    }
};
void Test()
{
    Car* pBenz = new Benz;
    pBenz->Drive();
    Car* pBMW = new BMW;
    pBMW->Drive();
}

3.2. Interface inheritance and implementation inheritance

The inheritance of ordinary functions is a kind of implementation inheritance. The derived class inherits the base class function and can use the function. What it inherits is the implementation of the function.
The inheritance of virtual functions is a kind of interface inheritance. The derived class inherits the interface of the base class virtual function. The purpose is to override and achieve polymorphism. What it inherits is the interface. So if you don't implement polymorphism, don't define the function as a virtual function.

4. Implementation Principles of Polymorphism

The key to "polymorphism" is that when a virtual function is called through a base class pointer or reference, it cannot be determined at compile time whether it is a function of the base class or a derived class. It can only be determined at runtime.

4.1. Virtual function table

class A
{
public:
    int i;
    virtual void Print() { } // 虚函数
};

class B
{
public:
    int n;
    void Print() { }
};

int main()
{
    A a;
    B b;
    cout << sizeof(a) << "," << sizeof(b);
    return 0;
}

In addition to the i member, the A object actually has a pointer_vfptr placed in front of the object (some platforms may place it at the end of the object, this depends on the platform) .

This pointer in the object is called a virtual function table pointer, or virtual table pointer for short. The virtual table pointer points to a virtual function table, or virtual table for short. Every class that contains a virtual function has at least one virtual table pointer.

What is stored in the virtual table is the address of the virtual function. Because Func1 and Func2 in the parent class are both virtual functions, the virtual table of the parent class object b stores the addresses of virtual functions Func1 and Func2.

Through the above knowledge, we discovered a few questions:

1. There is also a virtual table pointer in the derived class object d. The d object is composed of two parts. One part is the member inherited from the parent class. The other part of the virtual table pointer, which is the existing part, is its own member.

2. The virtual tables of the base class b object and the derived class d object are different. Here we find that Func1 has completed the rewriting, so the virtual table of d
is stored in Rewritten Derive::Func1, so the rewriting of virtual functions is also called overwriting, and overwriting refers to the overwriting of virtual functions
in the virtual table. Rewriting is called syntax, and overwriting is called principle layer.


3. In addition, Func2 is a virtual function after being inherited, so it is put into the virtual table. Func3 is also inherited, but it is not a virtual function
, so it is not put into the virtual table. surface.


4. The virtual function table is essentially a pointer array that stores virtual function pointers. Generally, a nullptr is placed at the end of this array.


5. Summarize the virtual table generation of derived classes: a. First copy the contents of the virtual table in the base class to the virtual table of the derived class. b. If the derived class overrides a virtual function in the base class, use the derived class The own virtual function covers the virtual function of the base class in the virtual table c. The newly added virtual functions of the derived class are added to the end of the derived class's virtual table in the order of declaration in the derived class.


6. Where do virtual functions exist? Where does the virtual table exist?

Virtual functions exist in virtual tables, and virtual tables exist in objects. Note that the answer above is wrong. Note that the virtual table stores virtual function pointers, not virtual functions. Virtual functions are the same as ordinary functions and are stored in the code segment, but their pointers are stored in the virtual table. In addition, what is stored in the object is not a virtual table, but a virtual table pointer.

1. The virtual table pointer is filled into the object during the constructor stage (initialization list), and the virtual table is generated at compile time.
2. The virtual function address is placed in the virtual table. Virtual functions are the same as ordinary functions. After compilation is completed, they are placed in the code segment.
3. All virtual functions in a class will be placed in the virtual table.
4. The subclass will copy the virtual table of the parent class, and then use the rewritten virtual function address to overwrite the function address in the original virtual table. Therefore, the rewriting of the virtual function also It's called virtual function override.
 

Guess you like

Origin blog.csdn.net/m0_69323023/article/details/134861857