BTP200

Virtual Functions and Abstract Classes

Summary

Polymorphism

Virtual Functions

Abstract Classes

Polymorphism

Polymorphism

Polymorphism stands for multiple forms

It is one of the main aspects of the Object-Oriented Paradigm

It allows the "same" operation to be performed differently depending on the type of object

Polymorphism

Imagine that we have an array of pointers to objects of different classes

Polymorphism means that, the same function call, applied to different elements of this array, will result in a different method being called

Early-binding

Polymorphism is hampered in C++, because it resolves function calls during compile time

I.e., each function call in your source code is mapped to the function definition before the application starts

This process is called early-binding

Early-binding

Example

#include <iostream>

const int N = 3;

class Animal
{
public:
    void speak() const
    {
        std::cout << "Animal sound!" << std::endl;
    }
};

class Dog : public Animal
{
public:
    void speak() const
    {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak() const
    {
        std::cout << "Meow!" << std::endl;
    }
};

void speak(const Animal &animal)
{
    animal.speak();
}

int main()
{
    Animal *animals[N];
    char choice = '\0';
    for (int i = 0; i < N; i++)
    {
        std::cout << "Want to add a dog [d] or cat [c]: ";
        std::cin >> choice;
        if (choice == 'd')
        {
            animals[i] = new Dog();
        }
        else
        {
            animals[i] = new Cat();
        }
    }

    for (int i = 0; i < N; i++)
    {
        speak(*animals[i]);
    }

    for (int i = 0; i < N; i++)
    {
        delete animals[i];
    }

    return 0;
}

              

Virtual Functions

Virtual Functions

The solution introduced in C++ was to introduce the keyword virtual

It can be applied to functions in a base class

It instructs C++ to implement dynamic dispatch, instead of early-binding

Dynamic dispatch means that the mapping from function call to function definition occurs during run time, based on the type of the object

Virtual Functions

Example

#include <iostream>

const int N = 3;

class Animal
{
public:
    virtual void speak() const
    {
        std::cout << "Animal sound!" << std::endl;
    }
};

class Dog : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Meow!" << std::endl;
    }
};

void speak(const Animal &animal)
{
    animal.speak();
}

int main()
{
    Animal *animals[N];
    char choice = '\0';
    for (int i = 0; i < N; i++)
    {
        std::cout << "Want to add a dog [d] or cat [c]: ";
        std::cin >> choice;
        if (choice == 'd')
        {
            animals[i] = new Dog();
        }
        else
        {
            animals[i] = new Cat();
        }
    }

    for (int i = 0; i < N; i++)
    {
        speak(*animals[i]);
    }

    for (int i = 0; i < N; i++)
    {
        delete animals[i];
    }

    return 0;
}

              

Virtual Functions

The keyword override was introduced in the C++11 Standard:

It enhances code safety by providing compile-time checking

It Improves code readability by indicating the intention of overriding a base class function

Design Considerations

To ensure that the destructor of the derived class is called, you should declare the base class destructor as virtual

Otherwise, the destructor of the base class might be called, leading to memory leaks

Design Considerations

Example

#include <iostream>

const int N = 3;

class Animal
{
public:
    virtual void speak() const
    {
        std::cout << "Animal sound!" << std::endl;
    }
    virtual ~Animal()
    {
        std::cout << "~Animal() called." << std::endl;
    }
};

class Dog : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Woof!" << std::endl;
    }
    ~Dog() override
    {
        std::cout << "~Dog() called." << std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Meow!" << std::endl;
    }
    ~Cat() override
    {
        std::cout << "~Cat() called." << std::endl;
    }
};

void speak(const Animal &animal)
{
    animal.speak();
}

int main()
{
    Animal *animals[N];
    char choice = '\0';
    for (int i = 0; i < N; i++)
    {
        std::cout << "Want to add a dog [d] or cat [c]: ";
        std::cin >> choice;
        if (choice == 'd')
        {
            animals[i] = new Dog();
        }
        else
        {
            animals[i] = new Cat();
        }
    }

    for (int i = 0; i < N; i++)
    {
        speak(*animals[i]);
    }

    for (int i = 0; i < N; i++)
    {
        delete animals[i];
    }

    return 0;
}

              

Abstract Classes

Abstract Classes

An abstract class is a base class that defines an interface

A concrete class is a derived class that implements that interface

The abstract class exposes the interface to its clients

Abstract Classes

An abstract class has at least one pure virtual function

The syntax to create a pure virtual function is:

virtual returnType functionName(arguments) = 0;

A pure virtual function does not need a definition - you can omit its implementation detail

Objects of an abstract class cannot be instantiated

Abstract Classes

Example

#include <iostream>

const int N = 3;

class Animal
{
public:
    virtual void speak() const = 0;
    virtual ~Animal()
    {
        std::cout << "~Animal() called." << std::endl;
    };
};

class Dog : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Woof!" << std::endl;
    }
    ~Dog()
    {
        std::cout << "~Dog() called." << std::endl;
    }
};

class Cat : public Animal
{
public:
    void speak() const override
    {
        std::cout << "Meow!" << std::endl;
    }
    ~Cat()
    {
        std::cout << "~Cat() called." << std::endl;
    }
};

void speak(const Animal &animal)
{
    animal.speak();
}

int main()
{
    Animal *animals[N];
    char choice = '\0';
    for (int i = 0; i < N; i++)
    {
        std::cout << "Want to add a dog [d] or cat [c]: ";
        std::cin >> choice;
        if (choice == 'd')
        {
            animals[i] = new Dog();
        }
        else
        {
            animals[i] = new Cat();
        }
    }

    for (int i = 0; i < N; i++)
    {
        speak(*animals[i]);
    }

    for (int i = 0; i < N; i++)
    {
        delete animals[i];
    }

    return 0;
}

            

Suggested Reading

Virtual Functions

Abstract Classes

Virtual Functions in C++