Click here to Skip to main content
15,903,012 members
Articles / Programming Languages / C
Article

Think before you code, Virtual Functions in C++

Rate me:
Please Sign up or sign in to vote.
4.58/5 (151 votes)
2 Sep 2008CPOL2 min read 88K   144   43
Virtual functions in C++.

Introduction

A few days back, I was doing a job, and unintentionally, I made a mistake in the code (What mistake? That I will explain in the detailed section of the article), and when I was caught by a bug and started de-bugging it, I was amazed how a little mistake can give a programmer a whole lot of pain. Yes, I made a mistake in the virtual function area. How? Let's find out........

Using the code

So, why do we need a virtual function? Everyone knows that. Let's say I have a base class and a few derived class as well; and all the derived classes shares a common function, and in the driver program, I do not want to make a big huge switch/if block. I want to iterate through all the derived types and want to execute the common member function. Like this:

C++
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class CommunicationDevices
{
 //Base class has some property, for this article I dont need those
 public:
   inline virtual void which(){
     cout<<"This is a common device..."<<endl;
   }
};

class MobilePhoneWithGSMSupport:public CommunicationDevices
{
  //Derived class also has some extended property, GSM related
  public:
    inline virtual void which(){
     cout<<"This is a Mobile Phone...GSM Supported"<<endl;
    }
};

class MobilePhoneWithCDMASupport:public CommunicationDevices
{
  //Derived class also has some extended property, CDMA related
  public:
    inline void which(){
      cout<<"This is a Mobile Phone....CDMA Supported"<<endl;
    }
};

class Landline:public CommunicationDevices
{
  //Derived class also has some extended property
  public:
    inline void which(){
      cout<<"This is a Landline Phone..."<<endl;
    }
};

class Iphone:public MobilePhoneWithGSMSupport
{
  //More specific IPhone Feature here
  public:
    inline void which(){
      cout<<"This is apple Iphone with AT&T connection, GSM Support only..."
          <<endl;
    }
};


void whichPhoneUserIsUsing(CommunicationDevices &devices){
  devices.which();
}

int main(){
 MobilePhoneWithGSMSupport user1;
 MobilePhoneWithCDMASupport user2;
 Landline user3;
 Iphone user4;
 whichPhoneUserIsUsing(user1);
 whichPhoneUserIsUsing(user2);
 whichPhoneUserIsUsing(user3);
 whichPhoneUserIsUsing(user4);
 return 0;
}

Here, the idea is simple. Since we are using a virtual function in the base class, the “whichPhoneUserIsUsing()” method can take a generic base class argument, and the proper method from the derived class gets accessed depending upon the actual type of the object. This is the beauty of virtual functions. Note that in the method “whichPhoneUserIsUsing()”, we used a reference to the base class as the argument: “CommunicationDevices &devices”, and from the driver (main()), we are passing the derived class' object while calling this function. This is normally called as Upcasting in C++. That is, we are going from the more specific type to the more generic type. And, this casting is type-safe always. As you expected, this code will produce the following o/p:

bash-3.2$ g++ -g -o hello code1.cpp

bash-3.2$ ./hello

This is a Mobile Phone...GSM Supported
This is a Mobile Phone....CDMA Supported
This is a Landline Phone...
This is apple Iphone with AT&T connection, GSM Support only...

Now, consider the following code, only a single character (believe me, just a single character) has been changed here from the previous code:

We just modified our method whichPhoneUserIsUsing() like this:

C++
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class CommunicationDevices
{
 //Base class has some property, for this article I dont need those
 public:
   inline virtual void which(){
     cout<<"This is a common device..."<<endl;
   }
};

class MobilePhoneWithGSMSupport:public CommunicationDevices
{
  //Derived class also has some extended property, GSM related
  public:
    inline virtual void which(){
     cout<<"This is a Mobile Phone...GSM Supported"<<endl;
    }
};

class MobilePhoneWithCDMASupport:public CommunicationDevices
{
  //Derived class also has some extended property, CDMA related
  public:
    inline void which(){
      cout<<"This is a Mobile Phone....CDMA Supported"<<endl;
    }
};

class Landline:public CommunicationDevices
{
  //Derived class also has some extended property
  public:
    inline void which(){
      cout<<"This is a Landline Phone..."<<endl;
    }
};

class Iphone:public MobilePhoneWithGSMSupport
{
  //More specific IPhone Feature here
  public:
    inline void which(){
      cout<<"This is apple Iphone with AT&T connection, GSM Support only..."
          <<endl;
    }
};


void whichPhoneUserIsUsing(CommunicationDevices devices){
  devices.which();
}

int main(){
 MobilePhoneWithGSMSupport user1;
 MobilePhoneWithCDMASupport user2;
 Landline user3;
 Iphone user4;
 whichPhoneUserIsUsing(user1);
 whichPhoneUserIsUsing(user2);
 whichPhoneUserIsUsing(user3);
 whichPhoneUserIsUsing(user4);
 return 0;
}

We just modified our method whichPhoneUserIsUsing() like this:

C++
void whichPhoneUserIsUsing(CommunicationDevices devices){

 devices.which();

}

and bang.................given below is the output:

bash-3.2$ g++ -g -o hello code2.cpp 

bash-3.2$ ./hello

This is a common device...
This is a common device...
This is a common device...
This is a common device...

bash-3.2$ vim code2.cpp

So, what gets wrong here?

Yes, you guessed it correctly, it's a famous copy-constructor problem. When the arguments are just a “CommunicationDevices” instead of a reference to it, the function says:

Hey Mr. Programmer, I am bound to create only a temporary object for this function (whichPhoneUserIsUsing()). I am no more responsible to take a reference, so I don't care what kind of actual object you are passing through; I will create a concrete “CommunicationDevices” object, and will copy only those segments from the actual object which are meaningful to me (i.e., which are part of the base class). And, will only invoke the “which” method for this temporary object. And hence, every time you call me, I will call the base class version (i.e., CommunicationDevices version) of the which() method.

This famous property is called Object Bisection, or Object Slicing. Cutting down the desired property from one object and copying it to a concrete base class object.

References:

  1. C++ Programming Language: Bjarne Stroustrup

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Rebaca Technologies
India India
int main(){
while(!isSleeping())
{
write_code();
}
return 0;
}

Comments and Discussions

 
GeneralMy vote of 3 Pin
ALOK KUMAR7-Feb-13 19:27
ALOK KUMAR7-Feb-13 19:27 
GeneralNot about virtual functions at all Pin
xawari15-Jun-12 2:40
xawari15-Jun-12 2:40 
QuestionNicely explained, its all about style Pin
Rahul Rajat Singh16-Feb-12 1:14
professionalRahul Rajat Singh16-Feb-12 1:14 
QuestionI don`t know why a C++ programer always forget to use pointer or reference to imply dynamic function? Pin
Carlos_never24-Sep-10 16:10
Carlos_never24-Sep-10 16:10 
GeneralSurprise?! Pin
rerser gfdg29-Jul-09 7:08
rerser gfdg29-Jul-09 7:08 
GeneralGood find Pin
Donsw5-Jun-09 1:45
Donsw5-Jun-09 1:45 
GeneralVery Good Pin
Akram Ben Hassan27-Apr-09 16:40
Akram Ben Hassan27-Apr-09 16:40 
GeneralMake them const Pin
Stephen Adrian Hill8-Sep-08 22:57
Stephen Adrian Hill8-Sep-08 22:57 
GeneralAdvanced OOP Pin
Martin ISDN8-Sep-08 21:32
Martin ISDN8-Sep-08 21:32 
Generalrefactor base code Pin
Casey Shaar8-Sep-08 11:48
Casey Shaar8-Sep-08 11:48 
Generalchange your habits Pin
J0ker7-Sep-08 17:25
J0ker7-Sep-08 17:25 
GeneralRe: change your habits Pin
programmersmind8-Sep-08 8:44
programmersmind8-Sep-08 8:44 
GeneralRe: change your habits Pin
Damir Valiulin8-Sep-08 10:19
Damir Valiulin8-Sep-08 10:19 
GeneralRe: change your habits Pin
J0ker11-Sep-08 17:49
J0ker11-Sep-08 17:49 
GeneralRe: change your habits Pin
T-Mac-Oz9-Sep-08 13:43
T-Mac-Oz9-Sep-08 13:43 
GeneralRe: change your habits Pin
T-Mac-Oz9-Sep-08 13:29
T-Mac-Oz9-Sep-08 13:29 
GeneralRe: change your habits Pin
J0ker11-Sep-08 7:50
J0ker11-Sep-08 7:50 
GeneralRe: change your habits [modified] Pin
T-Mac-Oz11-Sep-08 13:00
T-Mac-Oz11-Sep-08 13:00 
J0ker wrote:
De-referencing is only one operation (pick up object address from the stack).


Which gives you the base address of the object, additional address calculation then has to be performed to address each data member of that object (i.e. double the memory offset calculations of a structure passed on the stack).
E.g.
struct Cartesian
{
int x;
int y;
};
void DoOneThing(const Cartesian &c)
{
int tmp1 = c.y; // c - get object address from stack
// .y - calculate member offset from object address
int tmp2 = c.x; // c - get object address from stack
// .x - first member has same address as object so no offset calculation
}
void DoAnotherThing(Cartesian c)
{
int tmp1 = c.x; // address of c & therefore .x (on stack) is known
// & can be addressed more or less directly
int tmp2 = c.y; // only need to calculate member offset .y
// optimised case will do the calculation at compile time
// so access time is the same as for .x
}
The calculation of data member offsets also ties up registers that could otherwise be used for other optimisations.
J0ker wrote:
your "simple structure" should have only one "simple member variable"

This situation does have the greatest potential for benefit (when compiler does not optimise) but note also that the same benefits apply whenever a function accesses the first data member of an object with more than one data member.

In the vast majority of cases, the performance penalty of these additional operations is negligible compared to other bottlenecks that typically appear in a system.

Furthermore, the effort required to identify the very specific cases where stack-passing a structure might out-perform reference passing most often outweighs the potential for benefit. That is why I suggest leaving it to those who know what they're doing & only if it's absolutely necessary (e.g. squeezing the last ounce of performance out of a real-time embedded system). I'm only saying that the potential for performance gain exists in some rare cases, not that it's necessarily worth pursuing.

Finally, I'd just like to say - hey! I agree with you! Passing by reference is definitely the best practice. The potential for unintended side-effects when passing by value is tremendous and the effort expended to find such bugs can be substantial, is wholly unnecessary & could be better spent on such things as squeezing out incremental performance improvements Big Grin | :-D .

T-Mac-Oz

"When I'm ruler of the universe ... I'm working on it, I'm working on it. I'm just as frustrated as you are. It turns out to be a non-trivial problem." - Linus Torvalds

modified on Thursday, September 11, 2008 8:22 PM

GeneralRe: change your habits Pin
J0ker11-Sep-08 17:37
J0ker11-Sep-08 17:37 
GeneralRe: change your habits Pin
T-Mac-Oz11-Sep-08 18:42
T-Mac-Oz11-Sep-08 18:42 
GeneralRe: change your habits Pin
J0ker12-Sep-08 1:58
J0ker12-Sep-08 1:58 
GeneralNice One Pin
ioeilsk5-Sep-08 9:33
ioeilsk5-Sep-08 9:33 
GeneralRe: Nice One Pin
programmersmind11-Sep-08 4:09
programmersmind11-Sep-08 4:09 
GeneralC++ Gotchas vs Lippman and Meyers Pin
pg--az2-Sep-08 18:32
pg--az2-Sep-08 18:32 
GeneralA few words..... [modified] Pin
Dezhi Zhao2-Sep-08 11:57
Dezhi Zhao2-Sep-08 11:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.