Virtual Methods

Simple Is-A Relationship Is Not Enough

#include <iostream>


class Base
{
public:
    void method() const
    {
        std::cout << "Base::method()" << std::endl;
    }
};

class Derived : public Base
{
public:
    void method() const
    {
        std::cout << "Derived::method()" << std::endl;
    }
};

int main()
{
    Derived d;
    Base* b = &d;     // <--- **is-a**: Derived is converted to Base, **without** a cast
    b->method();      // <--- **Question**: Base::method() or Derived::method()?
    
    return 0;
}
$ ./inher-oo-derived-novirtual-base-usage
Base::method()

Questions

  • If Base* b actually points to a Derived object, why isn’t Derived::method() called?

  • If Derived is-a Base, how do I use it as-a Base?

  • In other words: how to I use a Base* to call Derived::method()

Usage Scenario

  • Consider a system that uses thermometers to measure temperature

  • There are a million of different thermometers out there that can answer me that question: what’s the temperature?

  • In software, I don’t want to care

    • I only want to know the temperature

    • I don’t want to expose the physics of thermometers to my system

Enter virtual

Answer

  • Use virtual on those methods that need dynamic dispatch

  • ⟶ through the Base*, the actual dynamic type is determined

#include <iostream>


class Base
{
public:
    virtual void method() const
    {
        std::cout << "Base::method()" << std::endl;
    }
};

class Derived : public Base
{
public:
    virtual void method() const
    {
        std::cout << "Derived::method()" << std::endl;
    }
};

int main()
{
    Derived d;
    Base* b = &d;
    b->method();       // <--- **dynamic dispatch**: calling Derived::method()!
    
    return 0;
}
$ ./inher-oo-derived-virtual-base-usage
Derived::method()

Question

  • Why isn’t that the default?

Answer

  • Performance ⟶ indirect function call cannot be default

Caution: virtual

  • C++ lets you complete freedom

  • virtual is just a mechanism to achieve dynamic dispatch

  • ⟶ no policy!

  • If you want to use Base* polymorphically, this is the place where virtual is written

Wrong

  • virtual has to come at dynamic dispatch entry point!

#include <iostream>


class Base
{
public:
    void method() const       // <--- ERROR: virtual omitted -> **no dynamic dispatch**
    {
        std::cout << "Base::method()" << std::endl;
    }
};

class Derived : public Base
{
public:
    virtual void method() const
    {
        std::cout << "Derived::method()" << std::endl;
    }
};

int main()
{
    Derived d;
    Base* b = &d;
    b->method();              // <-- WRONG: calling Base::method()!
    
    return 0;
}
$ ./inher-oo-derived-virtual-base-usage-wrong
Base::method()

Obscure but correct

  • virtual at dynamic dispatch entry point is sufficient

  • ⟶ propagates down the line

  • Not quite readable though

#include <iostream>


class Base
{
public:
    virtual void method() const
    {
        std::cout << "Base::method()" << std::endl;
    }
};

class Derived : public Base
{
public:
    void method() const       // <--- virtual omitted: correct but less readable
    {
        std::cout << "Derived::method()" << std::endl;
    }
};

int main()
{
    Derived d;
    Base* b = &d;
    b->method();              // <-- correct: calling Derived::method()
    
    return 0;
}
$ ./inher-oo-derived-virtual-base-usage-obscure
Derived::method()