Virtual functions in C++
Tags: cpp
Published: 2021-08-16 15:00:54

A virtual function in C++ is a function declared in a base class that allows overriding by its derived classes.

The main benefit of using virtual functions is the ability to achieve runtime polymorphism.

Without declaring a virtual function, if we were to try and override a regular function

```cpp
class Animal {
public:
    void describe_self() const {
        std::cout << "I'm an animal" << std::endl;
    }
};

class Dog : public Animal {
public:
    void describe_self() const {
        std::cout << "I'm a dog" << std::endl;
    }
};

class Cat : public Animal {
public:
    void describe_self() const {
        std::cout << "I'm a cat" << std::endl;
    }
};
```

and then we were to assign an object of class Dog to an Animal's pointer

```cpp
int main()
{
    Animal* doug = new Dog();
    doug->describe_self();
}

```

since the C++ compiler can only determine the type of the pointer at compile time it will bind the Animal pointer's describe_self function call to the method inside our base Animal class.

As such the following will print to console.

```
I'm an animal
```

This is because memory addresses of the Animal pointer and the Dog object are only resolved at runtime. This behaviour is known as early binding.

To prevent this from happening, and instead execute the most derived version of the called class, we can use a virtual function by adding the keyword virtual inside our base class method.

```cpp
class Animal {
public:
    virtual void describe_self() const {
        std::cout << "I'm an animal" << std::endl;
    }
};
```

This will correctly print

```
I'm a dog
```

It is also a good idea to add the C++11 keyword override in the derived classes methods for clarity and to prevent typos.

```cpp
class Dog : public Animal {
public:
    void describe_self() const override {
        std::cout << "I'm a dog" << std::endl;
    }
};

class Cat : public Animal {
public:
    void describe_self() const override {
        std::cout << "I'm a cat" << std::endl;
    }
};
```

Runtime polymorphism can be used for example in scenarios where you would want all of your objects from derived classes to execute the same method.

```cpp
int main()
{
    Animal* doug = new Dog();
    Animal* kat = new Cat();
    
    Animal* pets[2] = { doug, kat };

    for (int i = 0; i < 2; i++) {
        pets[i]->describe_self();
    }
}
```

This will print

```
I'm a dog
I'm a cat
```

Here we create an array of Animal pointers mapped to different Animal derived classes' objects, and we call their own describe_self method in a succint and scalable way.


There are also pure virtual functions that declare a placeholder method.

```cpp
class PureVirtual {
public:
	virtual void function() const = 0;
};
```

This abstract class forces any derived class to implement its own version of that function.
&nbsp;
&nbsp;

When a virtual function is declared and an object is instantiated from a class implementing it, the compiler will add a virtual pointer to the class object's data members and map it to a virtual table.

This virtual table is usually added at the class level, and it is an array containing pointers mapped to each virtual function.

To determine which function to call the runtime will follow these virtual pointers to the virtual table and finally to the function meant to be invoked.

In the above code our Dog and Cat class will each have a virtual table each containing an entry pointing to the describe_self method and our doug and kat objects will have a virtual pointer mapped to respectively our Dog and Cat's virtual tables.

Due to these extra steps virtual functions come at an extra cost, both in size and in speed.

The virtual tables in the classes, and the virtual pointers in the objects will take up extra memory, and for each function call at runtime we will have to follow the virtual pointers to the virtual table before reaching the correct address.

In short, virtual functions are a way to keep the code base more readable and more compact, but they incur a small cost that may be a factor when run on low-power embedded chips that can't sacrifice CPU cycles or other similar scenarios.