Dynamic Dispatch: Virtual Methods

Simple Is-A Relationship Is Not Enough

  • If Sensor* s actually points to a MySensor object, why isn’t MySensor::get_value() called?

  • If MySensor is-a Sensor, how do I use it as-a Sensor?

  • In other words: how to I use a Sensor* to call MySensor::get_value()

Enter virtual

  • Use virtual on those methods that need dynamic dispatch

  • Through the Sensor* (which points to an object of derived type MySensor), the actual dynamic type is determined

  • ⟶ Run Time Type Information (RTTI)

  • Question: why isn’t that the default?

  • Answer: performance ⟶ indirect function call cannot be default

#include <iostream>

class Sensor
{
public:
    Sensor(double value) : _value{value} {}
    virtual double get_value() const                   // <-- virtual: dynamic dispatch
    {
        std::cout << "Sensor::get_value(): " << _value << std::endl;
        return _value;
    }
private:
    double _value;
};

class MySensor : public Sensor
{
public:
    MySensor(double basetemp, double correction)
    : Sensor{basetemp},
      _correction{correction} {}

    double get_value() const
    {
        double value = Sensor::get_value() + _correction;
        std::cout << "MySensor::get_value(): " << value << std::endl;
        return value;
    }
private:
    double _correction;
};

Virtual Methods: Effects

  • Note how MySensor::get_value() is called through the base pointer

#include "sensors.h"
#include <iostream>

int main()
{
    MySensor ms{37.3, 0.25};

    Sensor* ps = &ms;

    double value = ps->get_value();                    // <-- dynamic dispatch to MySensor::get_value()
    std::cout << value << std::endl;

    return 0;
}
$ ./code/virtual-rudimentary/cxx-inher-oo-virtual-rudimentary
Sensor::get_value(): 37.3
MySensor::get_value(): 37.55
37.55

Virtual Methods: Where Not To Place 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

Attention

  • virtual has to come at dynamic dispatch entry point!

#include <iostream>

class Sensor
{
public:
    Sensor(double value) : _value{value} {}
    double get_value() const                           // <-- not a virtual method
    {
        std::cout << "Sensor::get_value(): " << _value << std::endl;
        return _value;
    }
private:
    double _value;
};

class MySensor : public Sensor
{
public:
    MySensor(double basetemp, double correction)
    : Sensor{basetemp},
      _correction{correction} {}

    virtual double get_value() const                   // <-- virtual, but too late down the hierarchy
    {
        double value = Sensor::get_value() + _correction;
        std::cout << "MySensor::get_value(): " << value << std::endl;
        return value;
    }
private:
    double _correction;
};
#include "sensors.h"
#include <iostream>

int main()
{
    MySensor ms{37.3, 0.25};

    Sensor* ps = &ms;

    double value = ps->get_value();                    // <-- **not** dynamically dispatched
    std::cout << value << std::endl;

    return 0;
}
$ ./code/virtual-wrong/cxx-inher-oo-virtual-wrong
Sensor::get_value(): 37.3
37.3

override: Problem

  • Common mistake in pre C++11: misspelled method signatures

  • E.g. double get_value() const and double get_value() are different methods

  • ⟶ Base method not overridden!

#include <iostream>

class Sensor
{
public:
    Sensor(double value) : _value{value} {}
    virtual double get_value() const                   // <-- virtual: dynamic dispatch
    {
        std::cout << "Sensor::get_value(): " << _value << std::endl;
        return _value;
    }
private:
    double _value;
};

class MySensor : public Sensor
{
public:
    MySensor(double basetemp, double correction)
    : Sensor{basetemp},
      _correction{correction} {}

    double get_value()                                 // <-- "const" omitted -> different method
    {
        double value = Sensor::get_value() + _correction;
        std::cout << "MySensor::get_value(): " << value << std::endl;
        return value;
    }
private:
    double _correction;
};
#include "sensors.h"
#include <iostream>

int main()
{
    MySensor ms{37.3, 0.25};

    Sensor* ps = &ms;

    double value = ps->get_value();                    // <-- *not* overridden by MySensor
    std::cout << value << std::endl;

    return 0;
}
$ ./code/virtual-override-problem/cxx-inher-oo-virtual-override-problem
Sensor::get_value(): 37.3
37.3

override: Solution

  • override, placed at the overriding method in the derived classes

  • Compiler then checks if something is overridden

  • Rules

    • virtual at the topmost definition (dynamic dispatch entry point)

    • override at every method that overrides/re-implements the base method

  • Note that override implies virtual, so virtual is not specified together with override

#include <iostream>

class Sensor
{
public:
    Sensor(double value) : _value{value} {}
    virtual double get_value() const                   // <-- virtual (topmost)
    {
        std::cout << "Sensor::get_value(): " << _value << std::endl;
        return _value;
    }
private:
    double _value;
};

class MySensor : public Sensor
{
public:
    MySensor(double basetemp, double correction)
    : Sensor{basetemp},
      _correction{correction} {}

    double get_value() const override                  // <-- overriding method
    {
        double value = Sensor::get_value() + _correction;
        std::cout << "MySensor::get_value(): " << value << std::endl;
        return value;
    }
private:
    double _correction;
};
#include "sensors.h"
#include <iostream>

int main()
{
    MySensor ms{37.3, 0.25};

    Sensor* ps = &ms;

    double value = ps->get_value();
    std::cout << value << std::endl;

    return 0;
}
$ ./code/virtual-override-solution/cxx-inher-oo-virtual-override-solution
Sensor::get_value(): 37.3
MySensor::get_value(): 37.55
37.55