A virtual function is a member function declared in a base class and redefined (overridden) by a derived class. When you reference a derived class object using a pointer or a reference to a base class, you can call a virtual function for the object and execute the function version of the derived class.
- Virtual functions ensure that the correct function is called for the object, regardless of the type of reference (or pointer) used for the function call.
- They are mainly used to implement Runtime polymorphism
- The function is declared in the base class using the virtual keyword.
- The parsing of function calls is done at run time.
Virtual function rule
- Virtual functions cannot be static.
- A virtual function can be a friend function of another class.
- Virtual functions should be accessed using pointers or references to base class types to achieve runtime polymorphism.
- The prototype of virtual function should be the same in base class and derived class.
- They are always defined in the base class and overridden in the derived class. Derived classes do not need to override (or redefine virtual functions), in which case the base class version of the function is used.
- A class can have virtual destructor , but cannot have a virtual constructor.
Compile time (early binding) VS run time (late binding) behavior of Virtual Functions
Consider the following simple program that shows the runtime behavior of virtual functions.
// CPP program to illustrate // concept of Virtual Functions #include<iostream> using namespace std; class base { public: virtual void print() { cout << "print base class\n"; } void show() { cout << "show base class\n"; } }; class derived : public base { public: void print() { cout << "print derived class\n"; } void show() { cout << "show derived class\n"; } }; int main() { base *bptr; derived d; bptr = &d; // Virtual function, binded at runtime bptr->print(); // Non-virtual function, binded at compile time bptr->show(); return 0; }
Output:
Print derived classes Show base class
**Note: * * runtime polymorphism can only be realized through pointers (or references) of base class types. In addition, the base class pointer can point to both the object of the base class and the object of the derived class. In the above code, the base class pointer "bptr" contains the address of the object "d" of the derived class.
The late binding (runtime) is based on the content of the pointer (i.e. the position pointed to by the pointer), and the early binding (compile time) is based on the type of the pointer. Because the print() function is declared with the virtual keyword, it will bind at runtime (the output is to print the derived class, because the pointer points to the object of the derived class) and show() is non virtual, Therefore, it will be bound at compile time (the output is the display base class, because the pointer is the basic type).
**Note: * * if we create a virtual function in the base class and it is overwritten in the derived class, we do not need to use the virtual keyword in the derived class, and the function is automatically regarded as a virtual function in the derived class.
The work of virtual functions (the concepts of VTABLE and VPTR) is just like here
As discussed, if a class contains a virtual function, the compiler itself does two things.
- If the object of this class is created, insert a * * virtual pointer (VPTR) * * as the data member of this class to point to the VTABLE of this class. For each new object created, a new virtual pointer is inserted as the data member of the class.
- Whether or not an object is created, the class contains an array of static function pointers called VTABLE as members. The cells of the table store the address of each virtual function contained in the class.
Consider the following example:
// CPP program to illustrate // working of Virtual Functions #include<iostream> using namespace std; class base { public: void fun_1() { cout << "base-1\n"; } virtual void fun_2() { cout << "base-2\n"; } virtual void fun_3() { cout << "base-3\n"; } virtual void fun_4() { cout << "base-4\n"; } }; class derived : public base { public: void fun_1() { cout << "derived-1\n"; } void fun_2() { cout << "derived-2\n"; } void fun_4(int x) { cout << "derived-4\n"; } }; int main() { base *p; derived obj1; p = &obj1; // Early binding because fun1() is non-virtual // in base p->fun_1(); // Late binding (RTP) p->fun_2(); // Late binding (RTP) p->fun_3(); // Late binding (RTP) p->fun_4(); // Early binding but this function call is // illegal (produces error) because pointer // is of base type and function is of // derived class // p->fun_4(5); return 0; }
Output:
Cardinality 1 Derivation 2 Base 3 Cardinality 4
**Description: * * initially, we create a pointer to the base class type and initialize it with the address of the derived class object. When we create an object of a derived class, the compiler will create a pointer as the data member of the class, which contains the address of the VTABLE of the derived class.
Like the example above, similar late and early binding concepts are used. For fun_ (1) function call, the base class version of the function is called, fun_2() is overridden in the derived class, so the derived class version is called, fun_3() is not overridden in the derived class and is a virtual function, so it is called the base class version, the same as fun_4() is not overwritten, so the base class version is called.
**Note: * * fun in derived class_ 4 (int) and the virtual function fun in the base class_ 4 () is different because the prototypes of the two functions are different.