Inheritance Basics

Sensor: Plain (Base) Class

  • A plain class Sensor with a get_value() method

  • No surprise

@startuml

class Sensor {
  + double get_value()
}
note left of Sensor::get_value
   Implemented somehow
end note

@enduml

#include <iostream>

class Sensor
{
public:
    Sensor(double value) : _value{value} {}
    double get_value() const
    {
        std::cout << "Sensor::get_value(): " << _value << std::endl;
        return 36.5;
    }
private:
    double _value;
};
#include "sensors.h"
#include <iostream>

int main()
{
    Sensor s{36.4};
    double value = s.get_value();
    std::cout << "got value " << value << std::endl;
    return 0;
}
$ ./code/base/cxx-inher-oo-base
Sensor::get_value()
got value 36.5

Inheriting (Deriving) From Base

News

  • MySensor is-a Sensor (though the usage of an is-a relationship is not at all clear yet)

  • Given an instance of Sensor, Sensor::method() is called

  • Given an instance of MySensor, MySensor::method() is called

Question

  • When ms is of type MySensor, but also of type Sensor (MySensor is-a Sensor), is it true that I can use ms as a Sensor?

  • If I use a MySensor object as-a Sensor, what is the effect of calling get_value() on it?

@startuml

class Sensor {
  + double get_value()
}

class MySensor {
  + double get_value()
}

Sensor <|-- MySensor

@enduml

#include <iostream>

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

class MySensor : public Sensor                         // <-- MySensor *is-a* 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;
};

Inheritance: Concrete Instances ⟶ No Surprise

  • Instantiation of concrete objects

  • Method calls directly on objects

  • ⟶ no surprise

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

int main()
{
    Sensor s{36.4};
    double value = s.get_value();                      // <-- Sensor::get_value()
    std::cout << "Sensor value: " << value << std::endl;

    MySensor ms{37.3, 0.25};
    value = ms.get_value();                            // <-- MySensor::get_value()
    std::cout << "MySensor value " << value << std::endl;

    return 0;
}
$ ./code/derived/cxx-inher-oo-derived-no-surprise
Sensor::get_value(): 36.4
Sensor value: 36.4
Sensor::get_value(): 37.3
MySensor::get_value(): 37.55
MySensor value 37.55

Inheritance: Automatic Pointer-To-Base Conversion

  • Definition of inheritance

  • Pointer-to-derived is automatically (no type cast needed) converted to pointer-to-base

  • Not obvious, and maybe unwanted: base class method called, although object is of derived type

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

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

    Sensor* ps = &ms;                                  // <-- Conversion to base class
                                                       // <-- *definition of inheritance*

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

    return 0;
}
$ ./code/derived/cxx-inher-oo-derived-pointer-conversion
Sensor::get_value(): 37.3

Inheritance: Slicing (Automatic Instance Conversion)

  • Instance conversion: derived to base

  • Rarely wanted

  • Slicing

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

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

    s = ms;                                            // <-- this is bad!

    double value = s.get_value();                      // <-- ???
    std::cout << value << std::endl;

    return 0;
}