第17回 解答例
センサの所有をunique_ptrに任せる
演習の解答例。まず自分で書いてから読むこと。
まず生ポインタで書く
#include <Arduino.h>
#include "IMU.hpp"
#include "Sensor.hpp"
void readOnce() {
Sensor* sensor = new IMU();
sensor->update();
sensor->print();
delete sensor;
}
void setup() {
Serial.begin(115200);
}
void loop() {
readOnce();
Serial.print("free heap: ");
Serial.println(ESP.getFreeHeap());
delay(1000);
}deleteを消すと何が起きるか
delete sensor;の行をコメントアウトして動かすと、ESP.getFreeHeap()の値が1秒ごとに減り続ける。
free heap: 294512 free heap: 294488 ← IMU1個分ずつ減っていく free heap: 294464 free heap: 294440 ... やがて確保に失敗して異常終了する
エラーも警告も出ない。プログラムは正常に動いているように見えたまま、時間をかけて死ぬ。これがリークのいちばん厄介なところである。
unique_ptrに書き換える
#include <memory>
void readOnce() {
std::unique_ptr<Sensor> sensor = std::make_unique<IMU>();
sensor->update();
sensor->print();
} // ここで解放される変わったのは宣言の1行で、deleteは消えた。getFreeHeap()は一定の値に戻る。
| 生ポインタ | unique_ptr | |
|---|---|---|
| 解放の記述 | deleteを自分で書く | 書かない |
途中でreturnした場合 | 解放されない | 解放される |
| 二重解放 | 起こせる | コピーがコンパイルエラーになる |
| ポインタの大きさ | 4バイト(ESP32-C5は32ビット) | 同じ |
vectorにまとめる
#include <Arduino.h>
#include <memory>
#include <vector>
#include "GPS.hpp"
#include "IMU.hpp"
#include "Sensor.hpp"
#include "ToF.hpp"
std::vector<std::unique_ptr<Sensor>> sensors;
void setup() {
Serial.begin(115200);
sensors.reserve(8);
sensors.push_back(std::make_unique<IMU>());
sensors.push_back(std::make_unique<GPS>());
sensors.push_back(std::make_unique<ToF>(6));
}
void loop() {
for (const std::unique_ptr<Sensor>& sensor : sensors) {
sensor->update();
sensor->print();
}
delay(1000);
}deleteは1行もない。sensorsが壊れるとき、持っている3つがすべて解放される。
shared_ptrは必要か
不要である。理由は、このプログラムでsensors以外にセンサを持つものが存在しないからである。
| 問い | この演習での答え |
|---|---|
| 持ち主は何人いるか | 1人(sensorsだけ) |
| どれが最後に手を離すか決まるか | 決まる(sensorsが消えるとき) |
| 生死を確認しながら使う相手がいるか | いない |
3つすべてが「はい」でないかぎりunique_ptrで足りる。shared_ptrにするとカウント用の領域が増え、コピーのたびに増減の処理が走るだけで、得るものがない。
shared_ptrが要るのは、たとえば「同じIMUを、姿勢制御と記録の2つが別々の寿命で参照する」ような場合である。その場合は第18回の循環参照にも気を配ることになる。
マイコンでの本当の答え
ここまでスマートポインタで書いてきたが、この演習の構成ではnew自体が不要である。
#include <array>
IMU imu;
GPS gps;
ToF tof(6);
std::array<Sensor*, 3> sensors = {&imu, &gps, &tof};
void loop() {
for (Sensor* sensor : sensors) {
sensor->update();
sensor->print();
}
}| 書き方 | ヒープ | 使うべき場面 |
|---|---|---|
静的に置いてSensor* | 使わない | 種類と個数がコンパイル時に決まる。既定の選択 |
unique_ptr+vector | 使う | 構成が実行時に決まる(接続されたセンサだけ使う等) |
shared_ptr | 使う | 持ち主が複数で、寿命を予測できない |