CPP as a second language

Virtual functions

Overview

Teaching: 10 min
Exercises: 5 min
Questions
  • What is a virtual function?

  • What is polymorphism?

Objectives
  • Explore virtual functions

  • See polymorphism in action

Previously we have been displaying multiple vectors at once, it would be nice to also include a label when we output them to identify that particular vector. Lets add a new function which takes a pointer to a Vector and a name and displays both.

First copy our last program to a new file and open the new file to create this function.

$ cp inheritance.cpp virtual.cpp
$ nano virtual.cpp
class Vector{...};

class Vec3:public Vector{...};

void nameAndDisplay(const char* name,Vector& v){
  std::cout<<name<<"=";
  v->display();
}

int main(){
 Vec3 a,b;
 a.data[0]=10;
 b.data[0]=22;
 Vector c=a+b;
 nameAndDisplay("a",a);
 nameAndDisplay("b",b);
 nameAndDisplay("c",c);
}

virtual.cpp

Lets test out this new function.

$ g++ virtual.cpp -o virtual
$ ./virtual
a=Vector: size=3, contents=(10,0,0)
b=Vector: size=3, contents=(22,0,0)
c=Vector: size=6, contents=(10,0,0,22,0,0)

Looking at the output we can see that the display member function of the original Vector class is being called even in the case of a and b which are declared as Vec3’s. What is going on here? When we call our nameAndDisplay function our Vec3s are being converted to Vectors. Since Vec3 inherits the Vector class this is actually allowed and is one of the nice things about inheritance. Without any extra work we can still use our nameAndDisplay function with our Vec3 objects even though they get converted to Vectors for use in the function. However, that means that in our nameAndDisplay function, the wrong display member function is getting called.

It would be nice if we did not have to create two nameAndDisplay functions one for each of Vector and Vec3. It turns out there is a way to do that. Using the virtual keyword. The virtual keyword can be used on a class member function to indicate that, at run time, it should check to see if this object is a class which was derived from the base class. If so, check to see if the virtual member function has been overridden in the derived class, if it has use that instead of the member function in the base class. In other words, we add the virtual keyword to our display member function in the Vector class and our nameAndDisplay function will call either the display defined in the Vector class if the passed object was originally a Vector or it will call the display member function defined in the Vec3 class if the object was originally a Vec3. That’s a pretty cool trick for a statically typed language like C++!

Lets try it out.

$ nano virtual.cpp
class Vector{
  int size;
  int* data;
  
  Vector operator+(Vector& rhs){...};
 ~Vector(){...}
  Vector(){...}
  Vector(int size_in){...}
  Vector(const Vector& vec_in){...}
  virtual void display(){...}
  void display(int num){...}
};

class Vec3:public Vector{...};

void nameAndDisplay(const char* name,Vector& v){...}

int main(){
 Vec3 a,b;
 a.data[0]=10;
 b.data[0]=22;
 Vector c=a+b;
 nameAndDisplay("a",a);
 nameAndDisplay("b",b);
 nameAndDisplay("c",c);
}

virtual.cpp

Note that the only addition we have made is the virtual keyword before the void display() function declaration in the Vector class.

Lets compile and run it and see what we get.

$ g++ virtual.cpp -o virtual
$ ./virtual
a=Vec3: (10,0,0)
b=Vec3: (22,0,0)
c=Vector: size=6, contents=(10,0,0,22,0,0)

When the pointer to a Vector class is treated differently depending on whether it points to a Vector class, or child classes is something that is referred to as polymorphism.

This is a pretty cool thing, in that you can have a bunch of objects of different classes being handled by the same function with potentially different behaviours if they all inherit the same base class.

Manual polymorphism

I would like to point out that this sort of polymophic flexibility can be achieved in a somewhat manual sense using the basic features of C. The example below utilizes void* pointers and casting between different pointer types. The file shown bellow, polymanual.cpp, demonstrates one way this similar level of flexibility of inheritance and virtual functions could be achieved. It should also be noted that while the below code may seem like it would obviously be less perfromant than the virtual method, this difference might not be as large as you think based purely on the amount of lines of code, as polymophism using the virtual keyword does still require run time lookups into virtual function tables. If possible it is advised to avoid virtual member functions in performance critical code, and of course always profile to know what is performance critical.

#include <iostream>

enum ObjectType{
  ObjectType_A,
  ObjectType_B,
};

struct A{
  int value;
};

struct B{
  int value;
};

struct Object{
  ObjectType type;
  void* obj;
};

void displayObject(Object* objToDisplay){
  switch(objToDisplay->type){
    case ObjectType_A:{
      A* temp=(A*)objToDisplay->obj;
      std::cout<<"A: value="<<temp->value<<"\n";
      break;
    }
    case ObjectType_B:{
      B* temp=(B*)objToDisplay->obj;
      std::cout<<"B: value="<<temp->value<<"\n";
      break;
    }
  }
}

int main(){
  A a;
  a.value=20;
  B b;
  b.value=30;
  
  Object obj;
  obj.type=ObjectType_A;
  obj.obj=&a;
  displayObject(&obj);
  
  obj.obj=&b;
  obj.type=ObjectType_B;
  displayObject(&obj);
}
$ g++ polymanual.cpp -o polymanual
$ ./polymanual
A: value=20
B: value=30

Key Points