C++ Language Basics (3)

22. Realization of polymorphism

Polymorphism generally refers to the polymorphism implemented by inheritance and virtual functions . For overloading, the principle is actually based on the different rules when the compiler generates symbol tables for functions. Overloading is just a language feature. Polymorphism has nothing to do with object-oriented, but this is a new rule added in C++, so it belongs to C++, so if you have to say that overloading is a kind of polymorphism, then you can say: multiple State can be divided into static polymorphism and dynamic polymorphism .

Static polymorphism is actually overloading, because static polymorphism means that which function to call is determined at compile time, based on the parameter list;

Dynamic polymorphism is realized by rewriting the virtual function of the parent class by subclasses . Because it is a function that is decided to be called during runtime, it is called dynamic polymorphism.

Under normal circumstances, when we do not distinguish between the two, the polymorphism we refer to refers to dynamic polymorphism.

The realization of dynamic polymorphism is related to virtual function table and virtual function pointer.

Extension: Does the subclass override the virtual function of the parent class? When the subclass inherits the parent class, the pure virtual function of the parent class must be rewritten , otherwise the subclass is also a virtual class and cannot be instantiated. The purpose of defining a pure virtual function is to implement an interface and play a normative role. Programmers who can regulate the inheritance of this class must implement this function.

23. Virtual function related (virtual function table, virtual function pointer), the realization principle of virtual function

First of all, let's talk about the appearance of polymorphism in C++. Add the keyword virtual keyword in front of the function of the base class. In the derived class, rewrite the function, and the corresponding function will be called according to the actual type of the object at runtime. . If the type of the object is a derived class, the function of the derived class (subclass) is called. If it is a base class, call the function of the base class.

In fact, when a class contains a virtual function, the compiler will generate a virtual function table for the class to save the address of the virtual function in the class. Similarly, the derived class inherits the base class, and the derived class itself Of course, there must be virtual functions, so the compiler will also generate its own virtual function table for derived classes. When we define a derived class object, the compiler detects that the type has a virtual function, so it generates a virtual function pointer for the derived class object, pointing to the virtual function table of the type, and the initialization of the virtual function pointer is done in the constructor.

In the future, if there is a pointer of the base class type pointing to the derived class, then when the virtual function is called, it will find the address of the virtual function according to the virtual function table pointer of the real object pointed to, and then the derived class can be called The virtual functions in the virtual function table of the class implement polymorphism in this way.

24. How should the compiler handle the virtual function table?

For derived classes, the process for the compiler to create a virtual function table is actually a total of three steps:

  • Copy the virtual function table of the base class. If it is multiple inheritance, copy the virtual function table of each base class with virtual functions.
  • Of course, there is also a virtual function table of the base class and the virtual function table of the derived class itself share a virtual function table, which is also called a base class as the main base class of the derived class.
  • Check whether the derived class has rewritten the virtual function in the base class, if so, replace it with the address of the rewritten virtual function;
    check whether the derived class has its own virtual function, if so, add its own virtual function to its own virtual function table.

Derived *pd = new D(); B *pb = pd; C *pc = pd; Among them, the pointer positions of pb, pd, and pc are different. It should be noted that the content of the derived class itself should be appended to the main After the memory block of the base class.
insert image description here

25. The reason why the destructor is generally written as a virtual function

Intuitively speaking, it is to reduce the possibility of memory leaks. For example, a pointer of a base class points to an object of a derived class. When it is ready to be destroyed after use, if the destructor of the base class is not defined as a virtual function, then the compiler will consider the type of the current object to be Base class, call the destructor of the base class The function address of the destructor of the object has long been bound as the destructor of the base class) Only the destructor of the base class is executed, and the content of the derived class itself will not be The method is destructed, causing a memory leak. If the destructor of the base class is defined as a virtual function, then the compiler can execute the destructor of the derived class according to the actual object, and then execute the destructor of the base class to successfully release the memory.

26. Why are constructors generally not defined as virtual functions?

  • Virtual function calls only need to know "partial" information, that is, only need to know the function interface, and do not need to know the specific type of the object. However, if we want to create an object, we only need to know the complete information of the object. In particular, the exact type of object to be created needs to be known, so constructors should not be defined as virtual functions;
  • And judging from the way that the current compiler implements virtual function polymorphism, the virtual function is called by finding the address of the virtual function through the virtual function table pointer of the object after instantiation. If the constructor is virtual, then the virtual function table pointer does not exist, and the corresponding virtual function table cannot be found to call the virtual function, then this call actually violates the principle of instantiation first and then call.

27. What happens when a virtual function is called in a constructor or destructor

In fact, virtual functions should not be called in constructors or destructors, because such calls will not actually bring the desired effect.

For example, there is a base class of animals, which defines a virtual function action_type() of the animal's own behavior, and calls this virtual function in the constructor of the base class.

This virtual function is rewritten in the derived class. We expect to call the virtual function implemented by each object according to the real type of the object, but in fact when we create a derived class object, we first create a derived class object The base class part of the class executes the constructor of the base class. At this time, the part of the derived class itself has not been initialized. For such uninitialized things, C++ chooses to treat them as a safety method method.

That is to say, the base class part of the derived class is constructed. The compiler will think that this is an object of the base class type, and then call the virtual function implementation in the base class type, which does not proceed in the way we want. . That is, the object will not become a derived class object until the derived class constructor is executed.

The same is true in the destructor. After the derived class executes the destructor, the members of the derived class are in an undefined state, so it is impossible to call the destructor in the execution of the base class. Methods overridden by derived classes. Therefore, we should not call virtual functions in constructors or destructors, even if they are called, they will not achieve the results we want.

28. The role of the destructor and how it works

The constructor only serves to initialize the value, but when instantiating an object, parameters can be passed through the instance, from the main function to other functions, so that other functions have values. As a rule, as long as you instantiate an object, the system will automatically call a constructor, even if you don't write it, the compiler will automatically call it once.

The role of the destructor is opposite to that of the constructor. It is used to undo some special task processing of the object, which can release the memory space allocated by the object; features: the destructor has the same name as the constructor, but ~ is added before the function.

The destructor has no parameters, no return value, and cannot be overloaded. There can only be one destructor in a class. The compiler also automatically calls the destructor when the object is destroyed. Each class must have a destructor, and the user can define a destructor, or the compiler can automatically generate a default destructor. General destructors are defined as public members of a class.

29. What is the execution order of the constructor? Execution order of destructors?

constructor order

  • Base class constructor. If there are multiple base classes, the order in which the constructors are called is the order in which the classes appear in the class derivation table, not the order in which they appear in the member initialization table.
  • Member class object constructor. If there are multiple member class objects, the order in which the constructors are called is the order in which the objects are declared in the class, not the order in which they appear in the member initialization table.
  • Derived class constructor.

destructor order

  • Call the destructor of the derived class;
  • Call the destructor of the member class object;
  • Call the destructor of the base class.

30. Pure virtual functions (applied to interface inheritance and implementation inheritance)

In fact, the emergence of pure virtual functions is to allow inheritance to occur in a variety of situations:

  • Sometimes we want a derived class to only inherit an interface with member functions
  • Sometimes we want the derived class to inherit both the interface of the member function and the implementation of the member function, and the member function can be rewritten in the derived class to achieve polymorphism
  • Sometimes we also hope that the derived class cannot override the default implementation when inheriting the member function interface and implementation.

In fact, the purpose of declaring a pure virtual function is to let the derived class only inherit the interface of the function, and the derived class must provide an implementation of this pure virtual function, otherwise the class containing the pure virtual function will be Abstract class cannot be instantiated.

For pure virtual functions, we can actually provide it with implementation code, but since abstract classes cannot be instantiated, the only way to call this implementation is to point out its class name in the derived class object to call.

31. Introduction to static binding and dynamic binding

Speaking of static binding and dynamic binding, we must first know the static type and dynamic type. The static type is the type it uses when it is declared in the program, and it is determined during compilation. Dynamic type refers to "the actual type of the object currently pointed to", which is determined during runtime.

Static binding, also known as early binding, is bound to a static type, and the corresponding function or property depends on the static type of the object, which occurs during compilation.

Dynamic binding, also known as late binding, is bound to a dynamic type, and the corresponding function or property depends on the dynamic type, which occurs during runtime.

For example, virtual functions are dynamically bound, non-virtual functions are statically bound, and default parameter values ​​are also statically bound. Here, we need to pay attention, we should not redefine the inherited default parameters, because even if we redefine, it will not work. Because a pointer of a base class points to an object of a derived class, the default value of the virtual function parameter is redefined in the derived class object, but the default parameter value is statically bound. What is bound is the content related to the static type, so there will be a virtual function implementation method of the derived class combined with the calling effect of the default parameter value of the base class, which is different from the expected effect.

32. The difference between deep copy and shallow copy

When there is an equal sign assignment of the class, the copy function will be called. If the explicit copy constructor is not defined, the system will call the default copy function—that is, shallow copy, which can complete the one-by-one member copy. Shallow copying works when there are no pointers in the data members.

But when there are pointers in the data members, if a simple shallow copy is used, the two pointers in the two classes point to the same address. When the object is about to end, the destructor will be called twice, resulting in pointer The problem of wild pointers.

Therefore, deep copy must be used at this time. The difference between deep copy and shallow copy is that deep copy will apply for additional space in the heap memory to store data, thus solving the problem of wild pointers . In short, when there are pointers in the data members, it must be safer to use deep copy.

Guess you like

Origin blog.csdn.net/qq_43679351/article/details/124971945