第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// 第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を外すと結果がどう変わるかも試してみるさせる