Ross Driedger
Source CodeVirtual Functions and Dynamic Binding
- Overloading Base Class Functions
- Static Binding
- Dynamic Binding and Polymorphism
- Abstract Classes
- Calling Base Class Implementations of Overridden Functions
- Object Management
- When to Use Virtual Functions
- Summary of Key Points
Overloading Base Class Functions
Let's suppose that we are writing a game called Total Warfare. In this game, characters can amass and use weapons of a wide variety of types and kinds. In order to keep the structure of objects under control, we are going to create a class called Weapon that has a member function use(). From this class we will derive other classes to represent more specialized kinds of weapons.
The basic UML Class Diagram looks like this:
Using inheritance, let us derive the following classes:
- RangeWeapon: This is a weapon that directs a projectile at the enemy.
- ChargeWeapon: This weapon directs an energy charge at the target.
- MeleeWeapon: This weapon is something that the user holds and thrusts at the target.
- BallisticWeapon: This is a weapon that is, at least for some time, powered and has the ability to accelerate against gravity when powered.
The four derived classes we have all have a function called use(). Invoking this function based on a derived class will cause the base class function to execute.
RangeWeapon wp; wp.use(); // Weapon::use() is called
Let's extend this inheritance tree by deriving more specific classes:
The code that creates this structure, instantiates objects of the derived classes and fires the weapons is in code listing 00:
//war.cpp int main() { M777_Howitzer mh; mh.use(); AK47 ak; ak.use(); Taser t; t.use(); BFG9000 bfg; bfg.use(); BowieKnife bk; bk.use(); PointyStick ps; ps.use(); ICBM icbm; icbm.use(); return 0; }
The resulting output looks as entirely expected based on the rules of the language and the code we have written:
c:\listing_00>war Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed.
Conceptually, though, this is strange. The concept of a weapon is abstract. It describes a classification of things. A weapons manufacturer does not make an item that is a weapon; it makes a more specific object that is classified as a weapon. Similarly, the idea of using a weapon can be described as abstract. Firing an ICBM is distinctly in procedure and effect different from stabbing with a pointy stick.
As a first attempt to fix this, let's remove the use() member function from the base class and implement them in the classes we instantiate (listing 01):
//weapons.h class Weapon { public: //delete the function in the base class //void use(); }; //Intermediate classes not shown class M777_Howitzer: public RangeWeapon { public: void use(); }; class AK47: public RangeWeapon { public: void use(); }; class Taser: public ChargeWeapon { public: void use(); }; class BFG9000: public ChargeWeapon { public: void use(); }; class BowieKnife: public MeleeWeapon { public: void use(); }; class PointyStick: public MeleeWeapon { public: void use(); }; class ICBM: public BallisticWeapon { public: void use(); };
Note that the use() function in the Weapon class has been commented out. The result of this is what we want – well, sort of:
c:\listing_01>war M777_Howitzer::use() executed. AK47::use() executed. Taser::use() executed. BFG9000::use() executed. BowieKnife::use() executed. PointyStick::use() executed. ICBM::use() executed.
Each class has its own implementation of use() and they are called correctly. Isn't this what we want?
Well, not really. In our programming scenario, a player can have a wide variety of weapons and this example we set the weapons during compile time. In C++ we keep a dynamic collection of objects of related classes by pointer or reference to a base class. Our code in the main() function looked like this:
//war.cpp int main() { M777_Howitzer mh; mh.use(); AK47 ak; ak.use(); Taser t; t.use(); BFG9000 bfg; bfg.use(); BowieKnife bk; bk.use(); PointyStick ps; ps.use(); ICBM icbm; icbm.use(); return 0; }
Because a player in our game can accumulated and lose weapons, this will not work for us. We will use an instance of std::vector to store pointers to Weapon and call use() on the pointer to the base class. Our main() looks like this (listing 02):
//war.cpp int main() { M777_Howitzer* mh = new M777_Howitzer; AK47* ak = new AK47; Taser* t = new Taser; BFG9000* bfg = new BFG9000; BowieKnife* bk = new BowieKnife; PointyStick* ps = new PointyStick; ICBM* icbm = new ICBM; vector<Weapon*> arsenal; arsenal.push_back(mh); arsenal.push_back(ak); arsenal.push_back(t); arsenal.push_back(bfg); arsenal.push_back(bk); arsenal.push_back(ps); arsenal.push_back(icbm); for(vector<Weapon*>::iterator it = arsenal.begin(); it != arsenal.end(); ++it) { Weapon* p = *it; p -> use(); } return 0; }
The only problem (and it is a big one) is that this is not valid C++ code, and it will not compile since we removed the use() member function from the Weapon class. See what we get:
c:\listing_02>nmake -f MakefileWin Microsoft (R) Program Maintenance Utility Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. cl -EHsc -c -O2 -W4 war.cpp Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. war.cpp war.cpp(31) : error C2039: 'use' : is not a member of 'Weapon' c:\listing_02\weapons.h(7) : see declaration of 'Weapon' NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN\amd64\cl.EXE"' : return code '0x2' Stop.
The compiler is expecting that the base class, Weapon, to have the use() function. In order to make this example work, we need to reestablish it in the base class. When we rerun the example after this we get:
c:\listing_02>war Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed. Weapon::use() executed.
Static Binding
Frustrating. It doesn't look like we are making any progress whatsoever. Let's take a look 'under the hood' to see what is going on.
When a program is loaded and starts a process, the operating system gives the process an area of memory in which to run[1]. This is called a memory address space. The memory address space is divided into a number of sections; the most important in our discussion are shown here:
The code segment is where all the instructions for the program are stored. Global and static data are stored in the data segment. The heap (or free store) holds data that has been dynamically allocated and the program stack holds data that is declared in a function or are arguments to a function.
When you create an object of type ICBM in the main() function the object is in the stack. When use() is called directly on that object, the compiler resolves this to the code in the code segment that holds the instruction for ICBM::use(). When an object of the same type is created on the heap and is referred to by pointer to the Weapon class, the compiler resolves it to the code for Weapon::use().
In terms of programming languages, this is referred to as static binding[2]. In static binding, the compiler resolves the connection between objects and the associated code at compile time. The major advantage of this is the speed of execution. One of the advantages of C++ is that it compiles into machine instructions and that well written code is close to optimally fast.
But clearly there will be situations where we need a way for an object to 'know' what kind of operations are appropriate to themselves even when they are being addressed through a base class pointer. Calling use() on an AK47 should use AK47::use(), even if we only have a pointer to Weapon.
A naïve approach would be to interrogate each object and call the appropriate function. While support for doing this exists in C++ (it is called runtime type information or RTTI), it leads to very poor code. The pseudo code for this would look something like this:
For each weapon { Get the type of weapon switch(type) { Case AK47: Fire weapon like an AK47 Case Pointy Stick: Use weapon like a pointy stick //etc… } }
The actual code of this in C++ is not included because it represents a very flawed approach to this issue for many good reasons:
- First of all, it makes for code that is very difficult to maintain. Every place in your program that weapons are fired will have to have a construct like this.
- switch() statements like this is something that OOP tries to eliminate.
- RTTI slows C++ code down considerably.
Dynamic Binding and Polymorphism
Fortunately, we can implement in C++ by using less extreme measures and adding this to the declaration of the base class (listing 03):
//in weapons.h
class Weapon
{
public:
//declaring this function as ‘virtual'
virtual void use();
};
When we execute the program we now see:
c:\listing_03>war M777_Howitzer::use() executed. AK47::use() executed. Taser::use() executed. BFG9000::use() executed. BowieKnife::use() executed. PointyStick::use() executed. ICBM::use() executed.
By declaring the member function in the base class as virtual[3], we get the desired result.
So what is happening inside our program to make this work? When you declare a virtual function in a base class a number of things happen:
- By declaring Weapon::use() as virtual, all classes derived from Weapon will have use() as virtual. If you declare some of your classes in this manner:
//weapons.h class Weapon { public: virtual void use(); }; class ChargeWeapon: public Weapon { }; class BFG9000: public ChargeWeapon { public: void use(); //not playing nicely with other programmers! };
The virtual nature of the function extends down to the BFG9000 class even though it is not explicitly declared in the class definition. This code compiles to the same as this:
class BFG9000: public ChargeWeapon { public: virtual void use();//that is much better. };
In other words, you cannot remove the 'virtual-ness' of a function when it has been declared as such in a more abstract class. The ChargeWeapon class also has a virtual function called use(), but it is the same one as for Weapon because it has not been overridden.
- Even though including the keyword virtual in a derived class makes no difference when the base class function is virtual, it is bad programming style not to include the word. When a programmer wants to use a class, he or she should not have to review the entire class hierarchy to determine which functions are virtual and which are not.
- Note that a function is only declared as virtual in the class definition (in the header file). If you are writing the implementation of the function outside the class definition, you do not include the keyword.
//weapons.h class BFG9000: public ChargeWeapon { public: virtual void use(); //virtual declaration made here! };
//weapons.cpp //No declaration here that the function is virtual! void BFG9000::use() { cout << "BFG9000::use() executed." << endl; }
- Declaring at least one virtual function in a class causes some changes to objects of that type. Along with the object's data members, a table of pointers to the virtual functions is added to the object. This is often referred to as the vtable. As a result, by adding virtual functions to a class, you increase the size of object's of that class.
- When a function is called on a pointer or reference to a base class, the system will first check in the vtable to see if there is a reference to that function. If so, then it will call the function that the vtable entry is pointing to. Otherwise, it will revert to the base class version of the function.
This is referred to dynamic binding. C++ is one of the few modern languages that allow programmer to choose between dynamic and static binding (C# is another). Languages of a more modern vintage, like Java, JavaScript and ActionScript, use dynamic binding exclusively. The reasons for this are many; one of them is historical. C++ was under development in the 1980s when CPUs were considerably less capable than the ones of today. Any chance to optimize the speed and size of the code was taken and static binding leads to smaller and faster code. For programs of small to moderate size, the extra time it takes for Java, JavaScript or ActionScript to execute with dynamic binding is not noticeable. As well, the computers of today have memory capacities that dwarf those of the 1980s. For all but large projects the kind of binding used will most likely not make much of a difference.
The major advantage of dynamic binding is that this allows the programmer to use polymorphism. This is exactly what we are doing in this exercise. An object 'knows' what kind of actions are appropriate to itself.
As another example, let's consider this scenario:
If we have a class called Animal and from that we derive two classes Quadruped (walks on 4 legs) and Biped (walks on 2 legs). From Quadruped we further derive Horse and Dog, and from Biped we create Human and TRex. The mechanics for the member function walk() are very different for each concrete class. To implement polymorphism, we can create objects of all these four concrete classes and yet each object will know its own proper procedure for walking.
The question is, then, why doesn't C++ implement dynamic binding as the default? As the capacity and speed of computers have increased, so have the demands of the operating systems and the associated libraries (like DirectX). As well, the gaming community is demanding better graphics, better physics, faster frame rates, better AI, and better game engines able to generate and control more enemies. The need to increase computational speed has not fallen off and will never drop away because computer games are constantly trying to meet the growing expectations of gamers.
Abstract Classes
One of the goals of OOP is to attempt to model the problem domain as closely as possible in code. Let's look at a simple use of our classes (listing 04):
//war.cpp int main() { Taser t; t.use(); Weapon w; w.use(); return 0; }
The result is:
c:\listing_04>war Taser::use() executed. Weapon::use() executed.
This makes sense with our code, but does it reflect reality? As we discussed earlier, the notion of something being 'just a weapon' is not based in our experience. Weapon is an abstract class; we make things that are more specific, like a Taser, which is a type of Weapon. If we want our classes to reflect reality more closely, we need to be able to create objects of type BFG9000 (which is a type of Weapon), but not a Weapon object directly.
In C++ we do this by making Weapon::use() a pure virtual function and making the class that holds it an abstract class. This is how we declare the class and function (listing 05):
// weapons.h class Weapon { public: virtual void use() = 0; }; class ChargeWeapon: public Weapon { public: }; class BFG9000: public ChargeWeapon { public: virtual void use(); };
We try to create an instance of Weapon:
//war.cpp int main() { BFG9000 bfg; bfg.use(); Weapon w; w.use(); return 0; }
We get this compiler error:
war.cpp(15) : error C2259: 'Weapon' : cannot instantiate abstract class due to following members: 'void Weapon::use(void)' : is abstract c:\listing_05\weapons.h(7) : see declaration of 'Weapon::use'
Plainly stated, because Weapon::use() is declared as pure virtual, an instance of this class cannot be created. Because BFG9000 has a version of use() that is specific to its class, an object of this type can be created. When we create classes that are derived from abstract classes, we need to write implementations of all pure virtual functions as described in the base classes. If we are designing a base class that is abstract, we can force any programmer who uses our class to implement a more specific functionality by making those functions pure virtual in the base class.
Note that if you declare a function to be pure virtual, you do not need to provide a body for the function. Weapon::use()is abstract, but there is, optionally, no implementation to this member. By declaring this as pure virtual, you are requiring any concrete base classes to have an implementation for this function, even if it is an empty function.
Calling Base Class Implementations of Overridden Functions
Just because a function is virtual, it doesn't mean there is some operation that needs to be done on a more abstract level. Let's consider the ChargeWeapon class (listing 06):
//weapons.h class Weapon { public: virtual void use() = 0; }; class ChargeWeapon: public Weapon { }; class Taser: public ChargeWeapon { public: virtual void use(); }; class BFG9000: public ChargeWeapon { public: virtual void use(); };
When we fire a charge weapon, the next thing the weapon does is to recharge its capacitors. This is not a notion of Weapon, the BFG9000 or the Taser. The best place to put this functionality would be in the ChargeWeapon class. We can do this by adding the use() member function to the ChargeWeapon class:
//weapons.h class ChargeWeapon: public Weapon { public: virtual void use(); };
Since this function is virtual and we are not creating objects that are directly of this class, this function will not be called unless we explicitly tell the system to do so. Here are the implementations use() for these classes:
//weapons.cpp void ChargeWeapon::use() { cout << "ChargeWeapon::use() executed." << endl; cout << "Starting to recharge...." << endl; } void Taser::use() { cout << "Taser::use() executed." << endl; //call to the base class implementation of use() ChargeWeapon::use(); } void BFG9000::use() { cout << "BFG9000::use() executed." << endl; //call to the base class implementation of use() ChargeWeapon::use(); }
When we fire the charge weapons...
//war.cpp int main() { BFG9000 bfg; bfg.use(); cout << endl; Taser t; t.use(); return 0; }
...we see that the base class function is called:
c:\listing_06>war BFG9000::use() executed. ChargeWeapon::use() executed. Starting to recharge.... Taser::use() executed. ChargeWeapon::use() executed. Starting to recharge....
We can extend this to the base class, even if the base class implementation for use() is pure virtual. Let's keep our Weapon::use() as pure virtual but supply a body for this function and call it from a derived class (listing 07):
class Weapon { public: //Note we are pure virtual virtual void use() = 0; }; class ChargeWeapon: public Weapon { public: virtual void use(); }; class Taser: public ChargeWeapon { public: virtual void use(); }; class BFG9000: public ChargeWeapon { public: virtual void use(); };
Now, the implementation of these functions:
//weapons.cpp //this is an implementation of a function //declared as pure virtual void Weapon::use() { cout << "Weapon::use() executed." << endl; cout << "...even though this function " << "is pure virtual...." << endl; } void ChargeWeapon::use() { cout << "ChargeWeapon::use() executed." << endl; cout << "Starting to recharge...." << endl; //call to the base class implementation of use() Weapon::use(); } void Taser::use() { cout << "Taser::use() executed." << endl; //call to the base class implementation of use() ChargeWeapon::use(); } void BFG9000::use() { cout "BFG9000::use() executed." << endl; //call to the base class implementation of use() ChargeWeapon::use(); }
//war.cpp int main() { BFG9000 bfg; bfg.use(); cout << endl; Taser t; t.use(); return 0; }
The result of this is:
c:\listing_07>war BFG9000::use() executed. ChargeWeapon::use() executed. Starting to recharge.... Weapon::use() executed. ...even though this function is pure virtual.... Taser::use() executed. ChargeWeapon::use() executed. Starting to recharge.... Weapon::use() executed. ...even though this function is pure virtual....
Object Management
So, are we finished here? Not really. Our recent examples have showed virtual functions when dealing with the concrete classes. The players will collect and lose weapons as the game progresses and to accommodate this we were dealing with a vector of pointers to weapons. Listing 08 has implemented all the classes in our structure to call the base class implementation of use(). In practice you will not have to go this far except in exceptional situations. This example is done like this to show what is possible, not necessarily what you should do all the time.
Let's review our main() function:
//war.cpp int main() { M777_Howitzer* mh = new M777_Howitzer; AK47* ak = new AK47; Taser* t = new Taser; BFG9000* bfg = new BFG9000; BowieKnife* bk = new BowieKnife; PointyStick* ps = new PointyStick; ICBM* icbm = new ICBM; vector<Weapon*> arsenal; arsenal.push_back(mh); arsenal.push_back(ak); arsenal.push_back(t); arsenal.push_back(bfg); arsenal.push_back(bk); arsenal.push_back(ps); arsenal.push_back(icbm); for(vector<Weapon*>::iterator it = arsenal.begin(); it != arsenal.end(); ++it) { Weapon* p = *it; p -> use(); cout << endl; } return 0; }
The result is;
c:\listing_08>war M777_Howitzer::use() executed. RangeWeapon::use() executed. Weapon::use() executed. AK47::use() executed. RangeWeapon::use() executed. Weapon::use() executed. Taser::use() executed. ChargeWeapon::use() executed. Weapon::use() executed. BFG9000::use() executed. ChargeWeapon::use() executed. Weapon::use() executed. BowieKnife::use() executed. MeleeWeapon::use() executed. Weapon::use() executed. PointyStick::use() executed. MeleeWeapon::use() executed. Weapon::use() executed. ICBM::use() executed. BallisticWeapon::use() executed. Weapon::use() executed.
So far so good except, as careful programmers, we are missing something important. We are allocating our objects in the free store but we are not deleting the objects. In this example, it does not matter too much, but in our game we cannot afford this kind of memory leak. Listing 09 loops though the allocated objects, deleting the weapons before the program exits. We have added an explicit destructor to each of the classes.
//weapons.h class Weapon { public: virtual void use() = 0; ~Weapon(); }; class RangeWeapon: public Weapon { public: virtual void use(); ~RangeWeapon(); }; class M777_Howitzer: public RangeWeapon { public: virtual void use(); ~M777_Howitzer(); }; class AK47: public RangeWeapon { public: virtual void use(); ~AK47(); }; //destructors are implemented for all classes.
The implementations:
//weapons.cpp //These functions are added for all classes Weapon::~Weapon() { cout << "Weapon dtor executed." << endl; } RangeWeapon::~RangeWeapon() { cout << "RangeWeapon dtor executed." << endl; } M777_Howitzer::~M777_Howitzer() { cout << "M777_Howitzer dtor executed." << endl; } AK47::~AK47() { cout << "BowieKnife dtor executed." << endl; } //destructors are implemented for all classes
The test code:
//war.cpp int main() { M777_Howitzer* mh = new M777_Howitzer; AK47* ak = new AK47; Taser* t = new Taser; BFG9000* bfg = new BFG9000; BowieKnife* bk = new BowieKnife; PointyStick* ps = new PointyStick; ICBM* icbm = new ICBM; vector<Weapon*> arsenal; arsenal.push_back(mh); arsenal.push_back(ak); arsenal.push_back(t); arsenal.push_back(bfg); arsenal.push_back(bk); arsenal.push_back(ps); arsenal.push_back(icbm); for(vector<Weapon*>::iterator it = arsenal.begin(); it != arsenal.end(); ++it) { Weapon* p = *it; delete p; cout << endl; } return 0; }
The resulting output is:
c:\listing_09>war Weapon dtor executed. Weapon dtor executed. Weapon dtor executed. Weapon dtor executed. Weapon dtor executed. Weapon dtor executed. Weapon dtor executed.
Something is not quite right with this. Let's recall some theory on destructors:
- Destructors are functions in which an object does a ‘clean up' of anything the object has manually allocated.
- They are named by the name of the class prepended by a tilde (~). They take no arguments and return nothing, not even void. They cannot be overloaded.
- If the programmer does not supply a destructor, the compiler will create an empty one automatically
- You don't explicitly call a destructor. It is called by the system when the object is no longer in scope or, in the case of objects created in the free store, when delete is called on the object[4]
- When an object is destroyed the destructors for the base classes are automatically called in the reverse order of the constructors when the object was created.
What is not happening in our example is that the destructors for the derived classes are being called. This is not what we want. If our derived classes allocate resources from the system, they will not be released because the destructors will not be called. What we want to see is something like this (listing 10).
//war.cpp int main() { ICBM icbm; return 0; }
This produces the following output:
c:\listing_10>war ICBM dtor executed. BallisticWeapon dtor executed. Weapon dtor executed.
This is the behaviour we want. When the execution moves out of the main() function, the local object moves out of scope and system calls the destructors of the object and its base classes in the correct order. When we call delete on an object through a base class pointer, then only the destructors that are part of that base class will be called.
The solution to this problem should be obvious: simply declare the destructor as virtual and we finally get the kind of behaviour we want: we can create objects dynamically, call use() confident that the system will execute the appropriate function and we can delete these objects without the possibility of something not being released properly (listing 11):
//weapons.h class Weapon { public: virtual void use() = 0; virtual ~Weapon(); }; class RangeWeapon: public Weapon { public: virtual void use(); virtual ~RangeWeapon(); }; class ChargeWeapon: public Weapon { public: virtual void use(); virtual ~ChargeWeapon(); };
//all destructors are made virtual //war.cpp int main() { M777_Howitzer* mh = new M777_Howitzer; AK47* ak = new AK47; Taser* t = new Taser; BFG9000* bfg = new BFG9000; BowieKnife* bk = new BowieKnife; PointyStick* ps = new PointyStick; ICBM* icbm = new ICBM; vector<Weapon*> arsenal; arsenal.push_back(mh); arsenal.push_back(ak); arsenal.push_back(t); arsenal.push_back(bfg); arsenal.push_back(bk); arsenal.push_back(ps); arsenal.push_back(icbm); for(vector<Weapon*>::iterator it = arsenal.begin(); it != arsenal.end(); ++it) { Weapon* p = *it; p -> use(); cout << endl; } for(vector<Weapon*>::iterator it = arsenal.begin(); it != arsenal.end(); ++it) { Weapon* p = *it; delete p; cout << endl; } return 0; }
c:\listing_11>war M777_Howitzer::use() executed. RangeWeapon::use() executed. Weapon::use() executed. AK47::use() executed. RangeWeapon::use() executed. Weapon::use() executed. Taser::use() executed. ChargeWeapon::use() executed. Weapon::use() executed. BFG9000::use() executed. ChargeWeapon::use() executed. Weapon::use() executed. BowieKnife::use() executed. MeleeWeapon::use() executed. Weapon::use() executed. PointyStick::use() executed. MeleeWeapon::use() executed. Weapon::use() executed. ICBM::use() executed. BallisticWeapon::use() executed. Weapon::use() executed. M777_Howitzer dtor executed. RangeWeapon dtor executed. Weapon dtor executed. BowieKnife dtor executed. RangeWeapon dtor executed. Weapon dtor executed. Taser dtor executed. ChargeWeapon dtor executed. Weapon dtor executed. BFG9000 dtor executed. ChargeWeapon dtor executed. Weapon dtor executed. BowieKnife dtor executed. MeleeWeapon dtor executed. Weapon dtor executed. PointyStick dtor executed. MeleeWeapon dtor executed. Weapon dtor executed. ICBM dtor executed. BallisticWeapon dtor executed. Weapon dtor executed.
Finally, some code that behaves and plays nicely with the system!
When to Use Virtual Functions
One kind of function that cannot be declared virtual is any constructor. If we reflect on this it makes perfect sense. A constructor is called when an object is being constructed, so when it starts to execute, the object is not fully formed. Recall that the address of the code that represents the virtual function is stored in the vtable and that is part of the object itself. There is no guarantee that the object has a viable vtable during the execution of the constructor. As a result, trying to make a constructor virtual will result is a compiler error.
While almost any function can be declared as virtual, it should be used with discretion and not overused. Since polymorphism and virtual functions are products of inheritance, it makes no semantic sense to declare private member functions to be virtual. A good rule of thumb is not to make a member function virtual unless the need for it is clear. Overuse of virtual functions will cause your process to consume more memory than necessary and slow it down.
Use virtual functions in situations where polymorphism is required. By overusing it, you take away from one of C++'s major advantages: speed of execution
Summary of Key Points
- Inheritance allows us to define a behaviour in a base class and override that behaviour in a concrete class derived from that base class.
- If we are working with collections of different types of objects with a common base class, we can manipulate the objects through base class pointers to those objects.
- Binding is how a programming languages associate objects with the code of their member functions. Static binding is done at compile time; dynamic binding happens at run time
- The advantages of static binding include execution speed and relatively low memory usage.
- Many modern languages use dynamic binding. Java, JavaScript and ActionScript are examples of this.
- C++ uses static binding by default, but a member function can be bound dynamically by making it virtual.
- By declaring a member function virtual in a base class, all overridden functions in derived classes will also be virtual
- By adding at least one virtual function to a class, objects of that class will contain a lookup table for the code for the virtual functions. This is called the vtable.
- In C++ the programmer can create an abstract base class (a class that cannot be directly instantiated) by declaring at least one pure virtual function. Derived classes must implement their own version of the pure virtual functions or the derived class itself will also be abstract.
- If the member function of a base class is overridden in a derived class, the base class function can be called from the overridden version by specifying the class and the function.
- If objects are managed through collections of base class pointers, the destructors need to be virtual.
- Almost any function can be made virtual: constructors are an exception.
- Virtual functions should be used only when needed.