C + + Getting Started tutorial 06

Learning links

6, Polymorphism

1. Overview of polymorphism

Polymorphism is one of the important characteristics of object-oriented programming. Literally, it can be simply understood as: multiple forms, multiple looks. In fact, the essential meaning is the same. In object-oriented programming, it means that when the same method is executed by different objects, it will have different execution effects.

Specifically, the implementation of polymorphism can be divided into two types: compile time polymorphism and run-time polymorphism. The former determines the specific operation process when compiling. The latter is an operation process that is only determined during program operation. This determines the operation process is binding, also known as binding.

The binding confirmed during compilation and connection is called static binding. The function overloading and function template instantiation we learned earlier belong to this category.

The other is that you can only confirm which code to execute when running, which is called dynamic binding. In this case, you can't confirm which code to run when compiling, but only after the program runs.

In contrast, static binding has high efficiency because it has been determined how to execute when compiling; Dynamic linking must be slower, but it has the advantage of flexibility.

Both have their own advantages and different use scenarios.

Next, take static binding as an example:

#include <iostream>
using namespace std;
#define PI 3.1415926

class Point
{
private:
    int x,y;

public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
    }
    double area()
    {
        return 0.0;
    }
};
class Circle:public Point
{
private:
    int r;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
    }
    double area()
    {
        return PI*r*r;
    }
};
  
int main()
{
  
    Point A(10,10);
    cout<<A.area()<<endl;
    Circle B(10,10,20);
    cout<<B.area()<<endl;
    Point *p;
    p = &B;
    cout<<p->area()<<endl;
    Point &pp=B;
    cout<<pp.area()<<endl;
    return 0;
  
}

Output results:

To analyze four outputs:

  • The area of the first cout output A is the area method in the Point class. The area is 0. There is no problem.
  • The area of the second cout output B is obviously the area method of the derived Circle. Naturally, the area is calculated according to the formula to obtain the value of 1256.64, which is no problem.
  • The third cout outputs the area method of the Circle class object pointed to by the Point type pointer p. it outputs 0, which obviously executes the area method in the Point class. Here, C + + implements static binding, that is, when compiling, it determines which area to execute according to the type of p, so it is 0
  • Similarly, the fourth cout assigns an object of type Circle to a reference of type Point. C + + also implements static binding and outputs 0

Obviously, this is not what we expect. In fact, for pointers and references, we prefer to execute the methods of actual objects rather than blindly determine the type of pointers and references. This is the problem if it is written like this.

If you want to meet our requirements, that is, no matter what type the pointer and reference are, you can make flexible decisions based on the object actually pointed to. Then it is necessary to change the default static binding method and adopt dynamic binding, that is, flexible decision at run time.

Let's look at the next section.

2. Virtual function

virtual function, what kind of function is this? In short, it is a function declared with virtual in front of a function. The general form is as follows:

virtual Function return value function name (formal parameter){
    Function body
}

The emergence of virtual functions allows the connection between the function and the function body to be established at runtime, that is, the so-called dynamic binding. Then, when the derived classes of virtual functions are running, they can implement one method according to dynamic binding, but there are different results, which is the so-called polymorphism. In this way, we can solve the problems in the previous section.

Next, we only need to declare the area method in the base class as a virtual function, so the pointer or reference of Point type in the main function can be called boldly, and there is no need to care about type problems. Because they will decide whose method to call according to the actual object type to realize dynamic binding.

#include <iostream>
using namespace std;
#define PI 3.1415926

class Point
{
private:
    int x,y;

public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
    }
    virtual double area()
    {
        return 0.0;
    }
};
class Circle:public Point
{
private:
    int r;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
    }
    double area()
    {
        return PI*r*r;
    }
};
  
int main()
{
  
    Point A(10,10);
    cout<<A.area()<<endl;
    Circle B(10,10,20);
    cout<<B.area()<<endl;
    Point *p;
    p = &B;
    cout<<p->area()<<endl;
    Point &pp=B;
    cout<<pp.area()<<endl;
    return 0;
  
}

Output results:

be careful:

  1. Virtual functions cannot be static member functions or friend functions because they do not belong to an object.
  2. An inline function cannot dynamically determine its position during operation. Even if a virtual function is defined inside a class, it will still be regarded as non inline during compilation
  3. Constructors cannot be virtual functions, destructors can be virtual functions, and are usually declared as virtual functions.

3. Virtual destructor

In C + +, the constructor cannot be defined as a fictitious constructor, because the constructor is called only when an object is instantiated, and the implementation of the virtual function is actually called through a virtual function table pointer. Of course, there is no object and no memory space, so the fictitious constructor before an object is instantiated is meaningless and cannot be implemented.

However, destructors can be virtual functions and are declared as virtual destructors most of the time. In this way, when using the pointer of the base class to point to the object of the derived class, you can dynamically associate and call the destructor of the subclass according to the actual object type to realize the correct object memory release.

#include <iostream>
using namespace std;
class Point
{
private:
    int x,y;
    int *str;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
        str = new int[100];
    }
    ~Point()
    {
        delete []str;
        cout<<"Called Point's Destructor and Deleted str!"<<endl;
    }
};
class Circle:public Point
{
private:
    int r;
    int *str;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
        str = new int[100];
    }
    ~Circle()
    {
        delete []str;
        cout<<"Called Circle's Destructor and Deleted str!"<<endl;
    }
};
int main()
{
    Point *p;
    p = new Circle(10,10,20);
    delete p;
    
    return 0;
}
// You can see that there is no destructor declared with virtual in the base class, and there is dynamic memory development in the base class and derived classes. Then we can also create a Circle class in the main function in the way of dynamic memory development, and then delete it

Output results:

It can be clearly seen that only the destructor of the base class is called, so the 4 * 100 bytes of memory from the new derived class will remain, resulting in memory leakage!

If the destructor in the base class is declared as virtual, the result is very different! At this time, the polymorphism effect occurs. It will first call to release the space of the derived class, and then release the memory space of the base class. It ends perfectly:

#include <iostream>
using namespace std;
class Point
{
private:
    int x,y;
    int *str;
  
public:
    Point(int x=0,int y=0)
    {
        this->x = x;
        this->y = y;
        str = new int[100];
    }
    virtual ~Point()    // Pay attention to this line
    {
        delete []str;
        cout<<"Called Point's Destructor and Deleted str!"<<endl;
    }
};
class Circle:public Point
{
private:
    int r;
    int *str;
public:
    Circle(int x,int y,int R):Point(x,y)
    {
        r = R;
        str = new int[100];
    }
    ~Circle()
    {
        delete []str;
        cout<<"Called Circle's Destructor and Deleted str!"<<endl;
    }
  
};
int main()
{
    Point *p;
    p = new Circle(10,10,20);
    delete p;
    
    return 0;
}

Output results:

Above, this is the benefit of virtual destructors.

4. Pure virtual functions and abstract classes

A pure virtual function is a virtual function without a function body.

This is the function defined as follows:

virtual Return value function name (formal parameter)=0;

You can see that the virtual function definition is the same as the virtual function definition, followed by a = 0. Indicates that there is no function body, which is a pure virtual function. A class containing pure virtual functions is an abstract class. An abstract class has at least one pure virtual function.

Abstract classes exist to provide a highly abstract and unified interface, and then use their own different methods through polymorphic characteristics. It is one of the core ideas of C + + object-oriented design and software engineering.

The characteristics of abstract classes are summarized as follows:

  1. An abstract class cannot instantiate an object. It can only be used as a base class, let the derived class improve the pure virtual function, and then instantiate it for use.
  2. The derivation of an abstract class can still be a pure virtual function in an imperfect base class and continue to be derived as an abstract class. Until the definitions of all pure virtual functions are given, they become a concrete class, and the object can be instantiated.
  3. Abstract classes cannot be used as parameter types, return values, or forced conversion types because they are abstract and cannot be materialized
  4. Then the third article, but the abstract class can define a pointer, reference type and point to its derived class to realize polymorphism.

Virtual function is a very important part of C + +, which must be deeply understood!

Tags: C++

Posted on Fri, 22 Oct 2021 23:14:08 -0400 by Agtronic