第11回

virtualとoverride

親クラスのポインタから、子クラスの実装を呼ぶ。

なぜvirtualが必要なのか

次のコードを考える。

class Sensor {
public:
    void update();
};

class IMU : public Sensor {
public:
    void update();
};

親クラスのポインタで扱った場合に、意図した派生クラスの関数が呼ばれるとは限らない。そこでvirtualを使う。

virtual

class Sensor {
public:
    virtual void update();
};
class IMU : public Sensor {
public:
    void update();
};

override

class IMU : public Sensor {
public:
    void update() override;
};

overrideは「親クラスのvirtual関数を上書きする」という意図をコンパイラに伝える。

ポインタからの呼び出し

IMU imu;

Sensor* sensor = &imu;

sensor->update();

update()がvirtualなら、実際のオブジェクトであるIMUの実装が呼ばれる。

             Sensor
               │
       ┌───────┴───────┐
       ↓               ↓
      IMU              GPS
       │                │
    update()         update()

変数の型(Sensor*)ではなく、実際に入っているもの(IMU)で呼び先が決まる。これが次回以降のポリモーフィズムの土台になる。

動かして確かめる

演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/11-virtual-override/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。

code/examples/11-virtual-override/
├── include/
│   ├── Sensor.hpp   virtual な update() / print()
│   ├── IMU.hpp      override する
│   └── GPS.hpp      override する
└── src/
    ├── IMU.cpp
    ├── GPS.cpp
    └── main.cpp
code/examples/11-virtual-override/src/main.cpp
// 第11回:親のポインタから子の実装を呼ぶ

#include <Arduino.h>

#include "GPS.hpp"
#include "IMU.hpp"
#include "Sensor.hpp"

IMU imu;
GPS gps;

// 上位のコードは「Sensorであること」しか知らない
Sensor* sensors[] = {&imu, &gps};
const int SENSOR_COUNT = sizeof(sensors) / sizeof(sensors[0]);

void setup() {
    Serial.begin(115200);
}

void loop() {
    for (int i = 0; i < SENSOR_COUNT; i++) {
        sensors[i]->update();
        sensors[i]->print();
    }

    delay(1000);
}
シリアルモニタの出力
IMU roll=0.10
GPS lat=0.0001
IMU roll=0.20
GPS lat=0.0002
IMU roll=0.30
GPS lat=0.0003

確かめること。

  • 同じsensors[i]->update()の行から、IMUの実装とGPSの実装が呼び分けられている
  • Sensor.hppのvirtualを外すと、どちらも親の実装が呼ばれるようになる。試して出力の変化を見る
  • IMU.hppのupdate()をupdata()と書き間違えると、overrideが付いているのでコンパイラが止めてくれる
  • Sensorにvirtual ~Sensor() = default;がある理由を確認する

演習:IMU / GPS

  • Sensorにvirtual void update();を用意する
  • IMUとGPSでそれぞれoverrideして、異なるシリアル出力をする
  • Sensor*に入れて呼び、どちらが呼ばれるかを確認する
  • virtualを外すと結果がどう変わるかも試してみるさせる