第10回

継承

共通する性質を親クラスにまとめ、個別の機能を子クラスに実装する。

継承なし

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

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

両方にupdate()が存在する。共通点がコード上どこにも表現されていない。

共通部分をSensorへ

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

「is-a」の関係

IMU is a Sensor
GPS is a Sensor

継承を使うべきか判断する際の重要な考え方。「AはBの一種である」と自然に言えないなら、継承ではなく「持つ(has-a)」関係を検討する。

protected

class Sensor {
protected:
    int value;
};

派生クラスから利用できる。外部には見せたくないが、子には使わせたいデータに使う。

動かして確かめる

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

code/examples/10-inheritance/
├── include/
│   ├── Device.hpp   共通部分(pin / begin / getPin)
│   ├── LED.hpp      Device を継承
│   └── Motor.hpp    Device を継承
└── src/
    ├── Device.cpp
    ├── LED.cpp
    ├── Motor.cpp
    └── main.cpp
code/examples/10-inheritance/include/Device.hpp
// 第10回:共通部分を親クラスにまとめる

#pragma once

class Device {
public:
    /**
     * @brief コンストラクタ
     * @param pin 使用するピン番号
     * @note explicit は「引数1つのコンストラクタを勝手な型変換に使わせない」指定。
     *       付けておくと Device device = 5; のような書き方を防げる。
     */
    explicit Device(int pin);

    /**
     * @brief ピンを初期化する
     * @details LED でもモータでも手順は同じなので、親に置ける。
     */
    void begin();

    /**
     * @brief ピン番号を返す
     * @return ピン番号
     */
    int getPin() const;

protected:
    // 子クラスからは使わせるが、外部には見せない
    int pin;
};
code/examples/10-inheritance/src/main.cpp
// 第10回:共通部分を親にまとめ、固有の機能を子に置く
//
//   Device
//    ├── LED
//    └── Motor
//
// begin() と getPin() は Device に1つだけ書いてある。
// LED も Motor もそれを書き直していない。

#include <Arduino.h>

#include "LED.hpp"
#include "Motor.hpp"

LED led(LED_BUILTIN);  // ボード上のRGB LED
Motor motor(24);       // モータはつないでいない。結果はシリアルの出力で見る

void setup() {
    Serial.begin(115200);
    delay(1000);

    // どちらも Device から継承した begin() を呼んでいる
    led.begin();
    motor.begin();

    // getPin() も Device に1つだけ書いてある。
    // LED 側は LED_BUILTIN(内蔵RGB LEDを指す特別な値)なので表示しない
    Serial.print("motor pin = ");
    Serial.println(motor.getPin());

    // 固有の機能は子クラスにある
    motor.setSpeed(60);
    motor.setSpeed(500);  // 丸められて 100 になる
}

void loop() {
    led.toggle();
    delay(500);
}
シリアルモニタの出力
Device::begin()
Device::begin()
motor pin = 24
Motor pin=24 speed=60
Motor pin=24 speed=100

確かめること。

  • Device::begin()が2回出ている。LEDもMotorもbegin()を書き直していないのに、どちらでも呼べている
  • LED.cppからdigitalWrite(pin, HIGH)と書けるのは、pinがprotectedだから。privateに変えるとコンパイルエラーになる
  • setSpeed(500)が100に丸められている。これはMotor固有の機能で、Deviceには置けない
  • RobotもDeviceを継承すべきか、「Robot is a Device」と声に出して確かめる

演習:クラス構造を考える

Device
├── LED
├── Motor
└── Servo
  • Deviceに共通して置けるものは何かを考える(ピン番号・初期化など)
  • それぞれ固有の機能は何かを書き出す
  • 「is-a」で説明できるかを確認する