[C++ keyword virtual]

1. The origin of the virtual keyword

In the early design of C++, member variables of derived classes can be accessed through base class pointers. This is because the layout of derived class objects in memory is that base class member variables come first, and derived class member variables come first. >After. Therefore, when we use a base class pointer to point to a derived class object, we can normally access the member variables inherited from the base class in the derived class.
However, with member functions, the situation is different. During compilation, member functions are not placed in the object’s memory space, but are stored in a separate memory area. Each class has only one copy of the member function code. When we call a member function through a base class pointer, the compiler will find the corresponding member function based on the static type of the pointer (that is, the base class type), rather than the dynamic type (that is, the derived class type that is actually pointed to). This results in us being unable to call member functions of derived classes through base class pointers.
In order to solve this problem, C++ introduced the concept of virtual function. By declaring the member functions of the base class as virtual functions, we can call the member functions of the derived class through the base class pointer, achieving so-called polymorphism. This is an important feature of C++ that supports object-oriented programming.

2. Conditions for polymorphism

Polymorphism is an important feature of object-oriented programming, which allows us to operate derived class objects through base class pointers or references. In C++, to achieve polymorphism, the following conditions need to be met:

  • There is an inheritance relationship: Polymorphism is based on inheritance, because only when there is a base class and a derived class, we can operate the derived class through the base class. This is thebasis of polymorphism.
  • The called function must be virtual: In C++, only member functions declared as virtual can achieve polymorphism. Virtual functions allow to be overridden in derived classes, so that when we call this function through a base class pointer or reference, the corresponding function will be called based on the actual object type. This is dynamic binding.
  • Virtual functions must be overridden: Overriding a virtual function means providing a function in the derived class that has the same function signature (i.e. function name and parameter types) as the base class virtual function. In this way, when we call this function through a base class pointer or reference, the version in the derived class will be called, not the version in the base class.

If all the above conditions are met, we can operate derived class objects through base class pointers or references to achieve polymorphism. This makes our code more versatile and extensible, because we can add new derived classes, and as long as they correctly override the base class’s virtual functions, they can be operated on by the same base class pointer or reference without having to Modify existing code.

3. Virtual function

A virtual function pointer is essentially just a pointer to a function, no different from an ordinary pointer. It points to the virtual function defined by the user, which is specifically implemented in the subclass. When the subclass calls the virtual function, it actually finds the interface by calling the virtual function pointer. Therefore, the core purpose of virtual functions is to access functions defined by derived classes through base classes.
The virtual function pointer is a data type that does exist. In an instantiated object, it is always stored at the first address of the object. The purpose of this approach is to ensure fast execution. Unlike members of an object, a virtual function pointer is completely invisible to the outside world. It is invisible and cannot be called by the outside world except by directly accessing the address or in DEBUG mode.
Only classes with virtual functions will have virtual function pointers, and each virtual function will also correspond to a virtual function pointer. Therefore, all objects of a class with virtual functions will incur additional overhead due to virtual functions, and will also reduce program speed to a certain extent. Unlike JAVA, C++ leaves the right to use virtual functions to developers, so developers should use them with caution.

class base
{<!-- -->
public:
 base();
 virtual void test(); //A virtual function defined
private:
 char *basePStr;
};

The above code defines a virtual function of test in the base class, so the behavior of redefining the parent class in its subclass becomes override, or rewriting.
virtual sample code:

#include <iostream>
using namespace std;
class A
{<!-- -->
public:
    void fool()
    {<!-- -->
        printf("1\\
");
    }
    virtual void fun()
    {<!-- -->
        printf("2\\
");
    }
    
};
class B : public A
{<!-- -->
public:
    void fool() //Hide, the functions of the derived class shield the functions of the base class
    {<!-- -->
        printf("3\\
");
    }
    void fun() //Polymorphism, covering the base class
    {<!-- -->
        printf("4\\
");
    }
};
int main(void){<!-- -->
    A a;
    B b;
    A *p = &a;
    p->fool(); //output 1
    p->fun(); //Output 2
    p = &b;
    p->fool(); //Depending on the pointer type, output 1
    p->fun(); //Depending on the object type, output 4, reflecting polymorphism
    B *p1 = & amp;b;
    p1->fool();//Depending on the pointer type, output 3
    return 0;
}

3.1 Characteristics of virtual functions

  • When a virtual function is defined in a base class, if the derived class does not define a new function to shadow this function, the virtual function of the base class will be used.
  • Declare the functions in the base class as virtual functions, so that all functions with the same name in the derived class that have a shadow relationship will automatically become virtual functions.
  • You only need to add the virtual keyword at the declaration of the virtual function, and you may or may not add it at the function definition.
  • Only when the virtual function of the derived class covers the virtual function of the base class (the function prototype is the same) can it constitute polymorphism (accessing the derived class function through the base class pointer).
  • Constructor cannot be virtual function. For the base class constructor, it is only called in the derived class constructor. This mechanism is different from inheritance. In other words, the derived class does not inherit the constructor of the base class, and it makes little sense to declare the constructor as a virtual function.

3.2 Generation of virtual function table

  • First copy the contents of the virtual table in the base class to the derived class virtual table;
  • If the derived class overrides a virtual function in the base class, use the derived class’s own virtual function to overwrite the virtual function of the base class in the virtual table;
  • The newly added virtual functions of the derived class are added to the end of the derived class’s virtual table in the order in which they are declared in the derived class.

3.3 Irregular rewriting behavior

  • The attribute of the virtual function is still maintained in the derived class, we just rewrite it, which is very irregular.
  • When rewriting a base class virtual function, the virtual function of the derived class without adding the keyword virtual can also constitute an override. However, this writing method is not standardized and is not recommended.

Supplement: The main purpose of virtual functions is to allow the implementation of a derived class to be called with a reference or pointer from the base class. This is called dynamic binding or delayed binding. If a function is not virtual, the compiler will resolve the function call at compile time, which is called static binding or early binding.

4. Abstract classes and pure virtual functions

In C++, a pure virtual function is a special virtual function that is not defined in the base class, only declared. The declaration form of a pure virtual function is as follows:

virtual return value type function name (function parameter) = 0;

Here = 0 means that this is a pure virtual function. Pure virtual functions are not implemented in the base class and need to be overridden in the derived class.
A class containing pure virtual functions is called an abstract class (also called an interface class). Abstract classes cannot be instantiated, that is, you cannot create an object of an abstract class. This is because the abstract class contains at least one function that is not implemented, so the object of the abstract class is incomplete.
When a derived class inherits an abstract class, if the derived class does not override all pure virtual functions, then the derived class is still an abstract class and cannot be instantiated. Only when the derived class overrides all pure virtual functions, the derived class is no longer an abstract class and can be instantiated.
The existence of pure virtual functions stipulates that all derived classes that inherit this abstract class must implement this function. This is the so-called interface inheritance. Interface inheritance emphasizes a set of public interfaces that derived classes must implement, rather than inheriting some already implemented behaviors.
A pure virtual function is the big leader of your company. His role is to scold Fang Qiu in the office, give guidance, inspire words, and give you a policy or strategy. However, he will not be able to do specific tasks and leave them to the people below. If the people below don’t do it, it will still be a policy and strategy without specific content.
In general,abstract classes and pure virtual functions are an important mechanism for object-oriented polymorphism, which allow base classes to define interfaces and leave the specific implementation to derived classes.
Pure virtual function example:

#include <iostream>
using namespace std;
class A
{<!-- -->
public:
    virtual void fool() = 0;
    virtual void fun() = 0; //Define 2 pure virtual functions
};
class B : public A
{<!-- -->
public:
    void fool() override //Hide, derived class functions shield base class functions
    {<!-- -->
        printf("3\\
");
    }
    void fun() //Polymorphism, covering the base class
    {<!-- --> //override can be written or not, it is best to add it
        printf("4\\
");
    }
};
int main(void){<!-- -->
    A *ptra; //base class pointer
    B *ptrb; // Derived class pointer
    B b; // Derived class instance
    ptra = & amp;b; //The base class pointer points to the derived class instance
    ptra ->fool();
    ptra ->fun();
    return 0;
}

Note:
Just because a class has virtual functions does not mean that it cannot create instances. Virtual functions allow derived classes to override member function implementations of base classes so that instances of the base class can call functions implemented by the derived class. Therefore, even if a class has virtual functions, you can create an instance of the class and call its member functions.
However, it should be noted that if a class has a pure virtual function (that is, it is defined but not implemented), then the class is an abstract class and cannot be instantiated. Pure virtual functions require derived classes to override the function implementation, so abstract classes can only be used as base classes and cannot directly create instances.

5. Virtual inheritance

In C++, the use of the virtual keyword in inheritance is mainly to solve the Diamond Problem in multiple inheritance.
The diamond inheritance problem means that in the process of multiple inheritance, a class may inherit the same base class through multiple paths. This will lead to duplication of members of the base class in the lowest derived class, resulting in a waste of resources and possible naming conflict.
When you use public Animal for inheritance, it’s ordinary public inheritance. If a class inherits from Animal through multiple paths, then there will be a copy of each instance of Animal in the lowest derived class. This can lead to ambiguity and unnecessary waste of resources.
For example, suppose you have the following class structure:

class Animal {<!-- -->
public:
    void eat();
};

class Mammal : public Animal {<!-- -->
};

class Bird : public Animal {<!-- -->
};

class Bat : public Mammal, public Bird {<!-- -->
};

In this example, the Bat class inherits the Animal class through the Mammal and Bird classes. This means that in the object of class Bat, there are two instances of class Animal. If you call the eat method of the Bat object, the compiler will not be able to determine which Animal instance’s eat method should be called, which creates ambiguity.
However, if you use virtual public Animal for inheritance, this is virtual inheritance. Virtual inheritance ensures that no matter how many paths a class inherits from the base class, there will be only one instance of the base class in the derived class. This solves the diamond inheritance problem.
Here is the version using virtual inheritance:

class Animal {<!-- -->
public:
    void eat();
};

class Mammal : virtual public Animal {<!-- -->
};

class Bird : virtual public Animal {<!-- -->
};

class Bat : public Mammal, public Bird {<!-- -->
};

In this example, the object of class Bat has only one instance of class Animal, so there is no ambiguity when calling the eat method.

6. Summary

There are roads in the mountain of books, and diligence is the path, and there is no limit to the sea of learning, and hard work is the boat.

7. Reprinted articles

  • 7.1 [C++ keyword virtual] C++ virtual keyword (declare member functions as virtual functions to achieve polymorphism
  • 7.2 Virtual functions and pure virtual functions