C++ basic tutorial virtual function instance code details

  • 2020-07-21 09:36:58
  • OfStack

Definition of virtual functions

Virtual functions: the member functions with the keyword virtual(modified by the keyword virtual) in front of the member functions of the base class and redefined in one or more derived classes; Virtual functions: When you compile, you don't know which function to call, but you dynamically decide which function to call. What it does is it allows this function to be overridden in its subclass, so that the compiler can use late binding to achieve polymorphism, which is calling this function in the subclass with a pointer to the base class; The function of virtual function is to realize polymorphism in professional terms. Polymorphism is to separate the interface from the implementation and access the member function with the same name in the derived class through the base class pointer or reference pointing to the derived class. To explain in visual language is to realize the common approach, but different strategies are adopted according to individual differences; virtual function return type function name (parameter table) {function body};

Virtual functions are an important concept in the c++ inheritance system, allowing us to copy superclass methods in subclasses. I don't know if there's an abstract class in c++ yet, but after virtual functions we know that abstract classes in java can be implemented using (pure) virtual functions,

Implementing virtual functions requires two steps of modification

In the parent class, the function GetName() is preceded by virtual In subclasses, add override after the function GetName()

Run the compiler again and you'll get the result you want

[

Shap rectangle

]

Let's start with the text

virtual function

The sample code is as follows:


#include <stdio.h>
class base {
public:
 virtual void name(){printf("base\n");};
 virtual ~base(){};
};
class plus: public base {
public:
 virtual void name(){printf("plus\n");};
};
void fv(base b){
 b.name();
}
void fp(base &b){
 b.name();
}
int main(){
 base b;
 plus p;
 fv(b);
 fv(p);
 fp(b);
 fp(p);
 return 0;
}

Program output:

[

base base base plus

]

There is an c++ point involved here -- upcast: Converting a derived class reference or pointer to a base class reference or pointer. This rule, which eliminates the need for display type transformation for public inheritance, is part of the ES42en-ES43en rule.

The opposite process is called -- downcast, and downcast must be displayed. Because derived classes may extend the base class, new member variables and functions cannot be applied to the base class.

Implicit upcast allows base class Pointers or references to point to base class objects or derived class objects, so dynamic linking is required. C++ USES virtual member function functions to meet this requirement.

Dynamic binding

The compiler will call the function corresponding to the corresponding executable code, this process is the function link (binding), in C++ because of function overloading, need to check the call function name and passed parameters to confirm which one function. At compile time, you can determine which functions are used for a link called static or early link.

Compilation is also made more complicated by the presence of the virtual function, as shown in the example function, which type of object to use cannot be confirmed. To do this, the compiler must generate code that selects the correct virtual function when the program is running, which is called dynamic link-up or late link-up.

To verify the above, we can make a set of controls. First, we use gnu tool nm to check sysbols, and we can find the following parts:

$ nm virtual.exe | grep -c -E "plus|base"

Then let's change 1 to the above code:


class base {
public:
 void name(){printf("base\n");}; //  Modify the 
 virtual ~base(){};
};
class plus: public base {
public:
 void name(){printf("plus\n");}; //  Modify the 
};

Re-execute the nm command after compilation:

[

nm virtual_.exe | grep -c -E "plus|base" 45

]

After comparison, we find that the following symbols is missing after modification:

[

000000000040509c p .pdata$_ZN4plus4nameEv 0000000000402e00 t .text$_ZN4plus4nameEv 00000000004060a0 r .xdata$_ZN4plus4nameEv 0000000000402e00 T _ZN4plus4nameEv

]

Dynamic linking is less efficient than static linking, which is the default in C++. strousstup, the father of C++, believes that C++ 1 is not to pay the price for features that are not used (cpu, memory, etc.).

So do not declare a derived class virtual function when it does not need to override the base class function.

How the virtual function works

Virtual functions represent every thing that developers using C++ are familiar with. One classic question is as follows:


#include <stdio.h>
class base
{
public:
 base(){};
 virtual ~base() { printf("base\n"); };
};
class plus : public base
{
public:
 plus(/* args */){};
 virtual ~plus() { printf("plus\n"); };
};
class plus2 : public base
{
public:
 plus2(/* args */){};
 ~plus2() { printf("plus2\n"); };
};
class plus3 : public base
{
public:
 virtual void name() { printf("plus3"); };
 plus3(/* args */){};
 virtual ~plus3() { printf("plus3\n"); };
};
class empty
{
private:
 /* data */
public:
 empty(/* args */){};
 ~empty() { printf("empty\n"); };
};
int main()
{
 base b;
 printf("base: %d\n", sizeof(b));
 plus p;
 printf("plus: %d\n", sizeof(p));
 plus2 p2;
 printf("plus2: %d\n", sizeof(p2));
 plus3 p3;
 printf("plus3: %d\n", sizeof(p3));
 empty e;
 printf("empty: %d\n", sizeof(e));
}

The final output is as follows:

[

base: 8 plus: 8 plus2: 8 plus3: 8 empty: 1 empty plus3 base plus2 base plus base base

]

ps: The pointer memory allocation size is 8 bytes in x64-bit systems and 4 bytes in x86 systems due to the influence of operating system bits.

We can clearly see that whenever there is a virtual function whether it's a member function or a destructor, defined or inherited in a class, there is a table of virtual functions. The 8 bytes here are the Pointers allocated to the vtable.

We can verify through gnu tool gdb instruction. After the breakpoint is triggered, we can check through info local command:


(gdb) info locals
b = {_vptr.base = 0x555555755d20 <vtable for base+16>}
p = {<base> = {_vptr.base = 0x555555755d00 <vtable for plus+16>}, <No data fields>}
p2 = {<base> = {_vptr.base = 0x555555755ce0 <vtable for plus2+16>}, <No data fields>}
p3 = {<base> = {_vptr.base = 0x555555755cb8 <vtable for plus3+16>}, <No data fields>}
e = {<No data fields>}

We can see that each object has a pointer to vtable.

When a base class declares a virtual function, the address of the function is added to the virtual function list when the object is created; if the derived class overwrites the function, the address of the new function is replaced; if the new function is defined, the pointer to the new function is added to the virtual table.

The sample code is as follows:


#include <stdio.h>
class base
{
public:
 base(){};
 virtual const char* feature(){return "test";};
 virtual void name() {printf("base\n");}
 virtual ~base() { printf("~base\n"); };
};
class plus : public base
{
public:
 plus(/* args */){};
 virtual void name() {printf("plus\n");}
 virtual void parant() {printf("base\n");}
 ~plus() { printf("plus\n"); };
};
int main()
{
 base b;
 printf("base: %ld\n", size_t(&b));
 plus p;
 printf("plus: %ld\n", size_t(&p));
}

Still using gdb to verify, after breakpoint through info vtbl command to see:


(gdb) info vtbl p
vtable for 'plus' @ 0x555555755d08 (subobject @ 0x7fffffffe010):
[0]: 0x555555554b4a <base::feature()>
[1]: 0x555555554bf8 <plus::name()>
[2]: 0x555555554c30 <plus::~plus()>
[3]: 0x555555554c66 <plus::~plus()>
[4]: 0x555555554c14 <plus::parant()>
(gdb) info vtbl b
vtable for 'base' @ 0x555555755d40 (subobject @ 0x7fffffffe008):
[0]: 0x555555554b4a <base::feature()>
[1]: 0x555555554b5c <base::name()>

When a virtual function is called, it looks for the corresponding function address in the virtual function table, so it does an extra step of matching every time it is called, which is more time-consuming than a static linked non-virtual function.

It is important to note that constructors cannot be declared as virtual functions, and it is recommended to declare a virtual destructor if a class ACTS as a base class unless it is not.

conclusion


Related articles: