Skip to content

Pure Virtual Functions and Interfaces in C++

Pure virtual functions allow us to define a function in a base class without providing an implementation, thereby forcing derived classes to implement that function.

In Object-Oriented Programming (OOP), it is very common to create a class that contains only unimplemented methods, which are then implemented by derived classes. This is often referred to as an interface. An interface is essentially a class that contains only unimplemented methods and serves as a template. Since the interface class does not contain any method implementations, we cannot instantiate it.

class Entity
{
public:
    virtual std::string GetName() = 0;   // Still defined as a virtual function, but the =0 makes it a pure virtual function
};

This means that if you want to instantiate this class, the function must be implemented in a derived class.

class Player : public Entity
{
private:
    std::string m_Name;
public:
    Player(const std::string& name)
        : m_Name(name) {}

    std::string GetName() override { return m_Name; } // If commented out, the class cannot be instantiated
};

void PrintName(Entity* entity)
{
    std::cout << entity->GetName() << std::endl;
}

int main()
{
    Entity* e = new Player("");  // Now it can be instantiated
    PrintName(e);
    Player* p = new Player("Cherno");
    PrintName(p);

After modifying it to Player, the class can now be instantiated simply because the Player subclass has implemented the GetName function. Therefore, it is clear that you can only instantiate a class that has implemented all pure virtual functions.

An Interface is just a class in C++.

#include <iostream>
#include  <string>

class Printable
{
public:
    virtual  std::string GetClassName() = 0;
};

class Entity : public Printable
{
public:
    virtual std::string GetName() { return "Entity"; }
    std::string GetClassName() override { return "Entity"; }
};

class Player : public Entity
{
private:
    std::string m_Name;
public:
    Player(const std::string& name)
        : m_Name(name) {}

    std::string GetName() override { return m_Name; }
    std::string GetClassName() override { return "Player"; }
};

void PrintName(Entity* entity)
{
    std::cout << entity->GetName() << std::endl;
}


class A : public Printable
{
public:
    std::string GetClassName() override { return "A"; }
};

void Print(Printable* obj)
{
    std::cout << obj->GetClassName() << std::endl;
}

int main()
{
    Entity* e = new Entity();
    Player* p = new Player("Cherno");

    Print(e);
    Print(p);
    Print(new A());

    std::cin.get();
}

By ensuring that a class has a specific method, we can pass an instance of that class as a parameter to a generic function, allowing us to call that function or perform other operations.