Lesson 10 | C++ Virtual Functions |
Objective | This page contains several guidelines for virtual functions along with several rules. |
Several Guidelines for Virtual function with Rules
Here's a set of essential guidelines to keep in mind when using virtual functions in C++ to achieve effective polymorphism:
When to Use Virtual Functions
- Overriding in Derived Classes: Use virtual functions when you intend to provide different specialized implementations of the function in derived classes. This is the fundamental mechanism for polymorphism in C++.
- Common Interface with Varied Behavior: Virtual functions are ideal when you want to work with a collection of objects of different derived types through a base class pointer or reference while allowing each object to perform its specific behavior.
Key Guidelines
- Declare Virtual in the Base Class: Always declare functions you want to be virtual using the `virtual` keyword in the base class.
Example:
virtual void print() { ... }
- Override in Derived Classes: To redefine the virtual function's behavior in a derived class, use the `override` keyword.
Example:
void print() override { ... }
- Pointers or References: To achieve polymorphic behavior, access the virtual function through a base class pointer or reference that points to an object of a derived class.
- Virtual Destructors: Make the destructor in the base class virtual if your derived classes perform dynamic memory allocation or other cleanup tasks. This ensures proper cleanup when a derived class object is deleted through a base class pointer.
- Pure Virtual Functions (Abstract Classes): To create interfaces and prevent instantiation of the base class itself, declare pure virtual functions using `= 0`. Example:
virtual void drawShape() = 0;
Additional Considerations
- Performance Overhead: Virtual function calls have a slight performance overhead compared to regular function calls due to the dynamic dispatch mechanism (looking up the function in the vtable at runtime). Consider this in performance-critical code.
- Avoid in Constructors: Don't generally call virtual functions within a constructor. Derived class parts of the object won't have been fully constructed yet, leading to potentially unexpected behavior.
Example Scenario
Consider a `Shape` base class with `draw()`. `Circle`, `Square`, and `Triangle` classes inherit from `Shape`, each implementing `draw()` differently. This allows you to manipulate a collection of `Shape*` pointers without worrying about the specific type of shape at compile time.
List several rules for working with virtual functions
Here are several rules and guidelines for working with virtual functions:
- Only nonstatic member functions can be virtual.
- The virtual characteristic is inherited. Thus, the derived class function is automatically virtual, and the presence of the virtual keyword is usually omitted.
- Constructors cannot be virtual.
- Destructors can be virtual. As a rule of thumb, any class having virtual functions should have a virtual destructor.
- C++ Object Construction:
In C++, a complete object is constructed one base class at a time. If we have a base class B and a class D derived from B. When constructing a D object, while executing B's constructor, the dynamic type of the object under construction is B. In particular, a call to a virtual function B:: Fun will hit B's definition of Fun, regardless of whether D overrides it or not.
Calling a D member function when the D object's members have not even been initialized yet would lead to unpredictable results.
Only after the construction of B has completed is D'sconstructor body executed and its identity as a D established. As a rule of thumb, keep in mind that during B's construction there is no way to tell whether the B is a standalone object or a base part of some other further-derived object; virtually-acting virtual functions would be such a way.
a call from a constructor to a pure virtual function that is not defined at all has undefined behavior.
Such code is therefore not only confusing, but it is also more fragile in the face of maintenance. On the other hand, some designs ask for post-construction, which is a virtual function that must be invoked right after the full object has been constructed.
C++ Virtual Functions
A virtual function is a member function that is declared within a base class and redefined by a derived class. To create a virtual function, precede the function's declaration in the base class with the keyword virtual. When a class containing a virtual function is inherited, the derived class redefines the virtual function to fit its own needs. In essence, virtual functions implement the "one interface, multiple methods" philosophy that underlies polymorphism. The virtual function within the base class defines the form of the interface to that function. Each redefinition of the virtual function by a derived class implements its operation as it relates specifically to the derived class. That is, the redefinition creates a specific method.
When accessed "normally," virtual functions behave just like any other type of class member function. However, what makes virtual functions important and capable of supporting run-time polymorphism is how they behave when accessed via a pointer.
A base-class pointer can be used to point to an object of any class derived from that base. When a base pointer points to a derived object that contains a virtual function, C++ determines which version of that function to call based upon the type of object pointed to by the pointer. And this determination is made at run time. Thus, when different objects are pointed to, different versions of the virtual function are executed. The same effect applies to base-class references.
To begin, examine this short example:
#include <iostream>
using namespace std;
class base {
public:
virtual void vfunc() {
cout << "This is base's vfunc().\n";
}
};
class derived1 : public base {
public:
void vfunc() {
cout << "This is derived1's vfunc().\n";
}
};
class derived2 : public base {
public:
void vfunc() {
cout << "This is derived2's vfunc().\n";
}
};
int main(){
base *p, b;
derived1 d1;
derived2 d2;
// point to base
p = &b;
p->vfunc(); // access base's vfunc()
// point to derived1
p = &d1;
p->vfunc(); // access derived1's vfunc()
// point to derived2
p = &d2;
p->vfunc(); // access derived2's vfunc()
return 0;
}
This program displays the following:
This is base's vfunc().
This is derived1's vfunc().
This is derived2's vfunc().