第16回 解答例
センサをstd::vectorでまとめる
演習の解答例。まず自分で書いてから読むこと。
解答
#include <Arduino.h>
#include <vector>
#include "GPS.hpp"
#include "IMU.hpp"
#include "Sensor.hpp"
#include "ToF.hpp"
IMU imu;
GPS gps;
ToF tof(6);
std::vector<Sensor*> sensors;
void setup() {
Serial.begin(115200);
sensors.reserve(8); // 8個分まで確保し直しが起きない
sensors.push_back(&imu);
sensors.push_back(&gps);
sensors.push_back(&tof);
}
void loop() {
for (Sensor* sensor : sensors) {
sensor->update();
sensor->print();
}
delay(1000);
}第13回との差
消えたのは次の1行である。
const int SENSOR_COUNT = sizeof(sensors) / sizeof(sensors[0]); // 不要になった同時にloop()からも添字と境界の判定が消えた。
// 第13回
for (int i = 0; i < SENSOR_COUNT; i++) {
sensors[i]->update();
}
// 第16回
for (Sensor* sensor : sensors) {
sensor->update();
}個数を人間が管理しなくなったので、個数の食い違いという種類のバグが消えた。これが今回いちばん大きい変化である。
初期化子リストで書く形
センサが最初から全部そろっているなら、setup()を使わずに書いてもよい。
std::vector<Sensor*> sensors = {&imu, &gps, &tof};この形は短いが、確保がグローバル変数の初期化の時点で起きる。setup()の中に寄せておくと、確保の順番とタイミングが追いやすい。
reserveを入れた理由
reserve()なしで3回push_back()すると、処理系によっては確保が複数回起きる。
reserve なし
push_back → 1個分 確保
push_back → 2個分 確保し直してコピー、1個分を解放
push_back → 4個分 確保し直してコピー、2個分を解放
↑ 使われない隙間がヒープに残る
reserve(8) あり
push_back → 確保済みの場所に置くだけ
push_back → 置くだけ
push_back → 置くだけ3個では実害は出ないが、「確保はsetupで1回だけ」という形を習慣にしておくと、センサが増えたときにそのまま通用する。
std::arrayにした場合
#include <array>
std::array<Sensor*, 3> sensors = {&imu, &gps, &tof};
void loop() {
for (Sensor* sensor : sensors) {
sensor->update();
sensor->print();
}
}loop()は1文字も変わらない。変わるのは宣言と、できなくなることである。
std::vector | std::array | |
|---|---|---|
| 個数の指定 | 書かない | <Sensor*, 3>と書く |
push_back() | 使える | 無い |
reserve() | 必要 | 不要(確保が起きない) |
| ヒープ | 使う | 使わない |
範囲for・size() | 使える | 使える |
つまりセンサの構成がコンパイル時に決まっているなら、この演習の正解はstd::arrayである。std::vectorが要るのは、たとえば「接続されているセンサを起動時に探索し、見つかったものだけ登録する」ように個数が実行時に決まる場合である。
void setup() {
sensors.reserve(8);
if (imu.begin()) {
sensors.push_back(&imu); // 繋がっていたセンサだけ登録する
}
if (gps.begin()) {
sensors.push_back(&gps);
}
}よくある間違い
// 良くない例1:vector の中身を値にしてしまう
std::vector<Sensor> sensors; // Sensorは抽象クラスなので、そもそも入れられない// 良くない例2:size() と int を比べて警告が出る
for (int i = 0; i < sensors.size(); i++) { // size() は符号なし
}
// 範囲forにすれば、この比較そのものが不要になる
for (Sensor* sensor : sensors) {
}// 良くない例3:loop() の中で毎回作り直す
void loop() {
std::vector<Sensor*> sensors = {&imu, &gps, &tof}; // 毎周 確保と解放
}3番目は動くが、ヒープの確保と解放を毎周繰り返すため、断片化の原因になる。std::vectorは寿命の長い場所に置く。