第12回
純粋仮想関数と抽象クラス
「必ず実装させる」仕組みとしてのインターフェース。
純粋仮想関数
class Sensor {
public:
virtual void update() = 0;
};= 0は「ここには実装がない。派生クラスが必ず実装すること」という意味である。
抽象クラス
Sensorそのものを直接オブジェクトとして扱わないことがある。そのとき、オブジェクトとして作れないようにする。具体的な実装は派生クラスで行う。
Sensor sensor; // エラー派生クラスで実装する。
class IMU : public Sensor {
public:
void update() override;
};インターフェースとして利用する
Sensor │ ├── IMU ├── GPS └── ToF
共通して、
update()を持つことを要求できる。新しいセンサを追加する人は、Sensorを継承した時点でupdate()の実装を強制される。
動かして確かめる
演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/12-abstract/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。
code/examples/12-abstract/
├── include/
│ ├── Sensor.hpp 純粋仮想関数だけ(= 0)。.cpp を持たない
│ ├── IMU.hpp
│ └── Barometer.hpp あとから足したセンサ
└── src/
├── IMU.cpp
├── Barometer.cpp
└── main.cpp// 第12回:共通の「約束ごと」だけを決めるクラス
#pragma once
class Sensor {
public:
virtual ~Sensor() = default;
/**
* @brief センサの値を読み直す
* @note = 0 なので、ここに実装はない。継承した側が必ず実装する。
*/
virtual void update() = 0;
/**
* @brief 現在の値をシリアルに表示する
*/
virtual void print() const = 0;
};// 第12回:抽象クラスは「約束ごと」。オブジェクトとしては作れない。
//
// 下の Sensor sensor; のコメントを外すとビルドが失敗する。
// エラーメッセージを一度自分の目で見てから、また戻すとよい。
#include <Arduino.h>
#include "Barometer.hpp"
#include "IMU.hpp"
#include "Sensor.hpp"
IMU imu;
Barometer baro;
// Sensor sensor;
// → error: cannot declare variable 'sensor' to be of abstract type 'Sensor'
// 純粋仮想関数が残っているので、実体は作れない
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Sensorを継承した2つは、どちらも update() と print() を持っている");
}
void loop() {
// どちらも「Sensorの約束ごと」を満たしているので、同じ順番で呼べる
imu.update();
imu.print();
baro.update();
baro.print();
Serial.println();
delay(1000);
}Sensorを継承した2つは、どちらも update() と print() を持っている
IMU roll=0.50 pitch=0.20
Baro pressure=1012.50 hPa
IMU roll=1.00 pitch=0.40
Baro pressure=1012.00 hPa確かめること。
// Sensor sensor;のコメントを外すとビルドが止まり、cannot declare variable 'sensor' to be of abstract type 'Sensor'と出るBarometer.cppからprint()の実装を消すと、Barometer baro;の行でエラーになる。実装を忘れられないSensor.hppに対応するSensor.cppがない。実装が1つもないので要らないIMUとBarometerは中身が全く違うのに、呼び出し方は同じになっている
演習
- 第11回の
Sensor::update()を純粋仮想関数に変える Sensor sensor;と書くとどうなるかを実際に確かめる- 新しく
ToFクラスを追加し、update()を実装する