コード読解演習 Step 2【中級】:ヘッダAPI仕様とクラス依存の解読
ヘッダファイル群(.h)からクラス設計図を脳内復元し、責任境界と呼び出し構造を見抜く
ファイル数が数十個に増えたとき、.cpp の実装ロジックにいきなり飛び込むのは遭難の元!ヘッダファイル(.h)の宣言だけを拾い読みしてクラス間の依存関係(所有・参照)と責務を瞬時に把握する中級読解テクニックを習得します。
2.1 中級読解の黄金律:「.cpp」は後回し!まず「.h」だけを読め
ファイル数が多いプロジェクトでは、実装詳細(.cpp)を読んではいけません。ヘッダの public メソッドとメンバ変数からシステムの骨格を掴みます。
🐧 ペンギン先輩
シロクマ先生、今度はファイルが10個に分かれてます!`SecurityRobot.cpp` を開いたら、長々とした計算処理が書かれていて、何をしているクラスなのか全体像が全然見えてきません……!
🐻❄️ シロクマ先生
渇っ!なぜいきなり `.cpp` を開くのじゃ!
`.cpp` は「エンジンの配線工事」じゃ。設計を見るにはまず「車のカタログ」である `.h`(ヘッダ)を見ねばならん!
ヘッダの **【① クラス名】→【② public 関数一覧(提供する機能)】→【③ メンバ変数(他クラスとの所有関係)】** をスキャンすれば、5分で脳内にクラス図が完成するのじゃ!
### ヘッダ(.h)スキャンのチェックリスト
1. **クラス名と責務**:
- `class SecurityRobot`: このクラスは何を担当する存在か?
2. **public 窓口関数(外部への約束)**:
- どんなメソッドを提供しているか?(例: `patrolStep()`, `reportStatus()`)
- 引数と戻り値は何か?
3. **private メンバ変数(他クラスへの依存性)**:
- 他のクラスを「値」で持っているか?(`Battery m_battery` → コンポジション、寿命連動)
- 他のクラスを「ポインタ/参照」で持っているか?(`Sensor* m_sensor` → 外部利用・集約)
💡 このセクションの重要ポイント
- 「カタログ(.h)」を見てから「中身(.cpp)」へ進む: ヘッダファイルだけでクラスの責務と依存関係の90%は理解できます。実装詳細(アルゴリズム)は必要になった時にだけ読みに行きます。
2.2 演習コード:自律型警備ロボットシステム一式
ヘッダファイル群と実装コードを切り替えて、上のUMLクラス図とコード行の対応関係(連動クリック)を体感してください。
📄 Battery.h
#pragma once
class Battery {
private:
int m_capacityMah;
int m_currentMah;
public:
Battery(int capacity)
: m_capacityMah(capacity), m_currentMah(capacity) {}
bool consume(int amount) {
if (m_currentMah < amount) return false;
m_currentMah -= amount;
return true;
}
int getPercentage() const {
return (m_currentMah * 100) / m_capacityMah;
}
};
📄 UltrasonicSensor.h
#pragma once
class UltrasonicSensor {
private:
int m_maxDistanceCm;
public:
UltrasonicSensor(int maxDist = 200) : m_maxDistanceCm(maxDist) {}
int pingDistanceCm() {
// シミュレーション用ダミー測定値(実際はハードウェアレジスタ読取)
return 45;
}
};
📄 SecurityRobot.h
#pragma once
#include "Battery.h"
#include "UltrasonicSensor.h"
class SecurityRobot {
private:
// 【コンポジション:内蔵】
Battery m_battery;
// 【関連:外付けセンサへの参照】
UltrasonicSensor* m_sensor;
int m_posX;
public:
SecurityRobot(int batteryCap, UltrasonicSensor* sensor);
void patrolStep();
void reportStatus() const;
};
📄 SecurityRobot.cpp
#include <iostream>
#include "SecurityRobot.h"
SecurityRobot::SecurityRobot(int batteryCap, UltrasonicSensor* sensor)
: m_battery(batteryCap), m_sensor(sensor), m_posX(0) {}
void SecurityRobot::patrolStep() {
// バッテリー消費チェック
if (!m_battery.consume(5)) {
std::cout << "[Robot] バッテリー切れのため停止!\n";
return;
}
// センサによる前方監視
if (m_sensor != nullptr) {
int dist = m_sensor->pingDistanceCm();
if (dist < 50) {
std::cout << "[Robot] 前方障害物を検知 (" << dist << "cm)!回避旋回します。\n";
return;
}
}
m_posX += 1;
std::cout << "[Robot] 前進中... 現在地点: " << m_posX << "m\n";
}
void SecurityRobot::reportStatus() const {
std::cout << "[Status] 位置: " << m_posX << "m, 残バッテリー: "
<< m_battery.getPercentage() << "%\n";
}
📄 main.cpp
#include <iostream>
#include "SecurityRobot.h"
int main() {
UltrasonicSensor externalSensor(200);
// ロボットにセンサのポインタを注入
SecurityRobot robot(1000, &externalSensor);
std::cout << "=== 自律警備システム 巡回開始 ===\n";
for (int i = 0; i < 3; i++) {
robot.patrolStep();
robot.reportStatus();
}
return 0;
}
💡 このセクションの重要ポイント
- 「メンバ変数の型」が関係性の正体: `Battery m_battery;`(生の実体)は寿命が一緒のコンポジション、`Sensor* m_sensor;`(ポインタ)は外から渡される関連関係。ヘッダの変数を見るだけで設計図が描けます。
📝 理解度チェッククイズ
Q1. SecurityRobot クラスにおける Battery と UltrasonicSensor の所有関係の違いとして正しいものはどれでしょう?
- 1. Battery も UltrasonicSensor もどちらも動的確保(new)されている
- 2. Battery はメンバ変数として直接値を持つコンポジション(内蔵)であり、UltrasonicSensor はポインタとして外部から渡される関連関係である
- 3. Battery はロボットの親クラス(継承)である
- 4. UltrasonicSensor は static メンバである
正解と解説を見る
正解:Battery はメンバ変数として直接値を持つコンポジション(内蔵)であり、UltrasonicSensor はポインタとして外部から渡される関連関係である
正解です!`Battery m_battery;` はロボットオブジェクト内に直接埋め込まれており寿命を共にします。一方 `UltrasonicSensor* m_sensor;` はアドレスを保持するポインタであり、センサの実体は外部(main関数内)に存在します。
Q2. SecurityRobot::patrolStep() において、ロボットが前進(m_posX += 1)せずに停止または旋回する条件はどれでしょう?
- 1. バッテリー残量が足りない場合、またはセンサ検知距離が 50cm 未満の場合
- 2. 走行距離が 100m を超えた場合のみ
- 3. センサのポインタが nullptr の場合のみ
- 4. 常に前進する
正解と解説を見る
正解:バッテリー残量が足りない場合、またはセンサ検知距離が 50cm 未満の場合
正解です!コード内の `if (!m_battery.consume(5))`(バッテリー切れ)と `if (dist < 50)`(障害物検知)の2つの早期リターン条件があります。
Q3. プロジェクト内で「SecurityRobot の機能に新しい命令(例: 緊急停止 emergencyStop())を追加したい」場合、最初に見るべき・修正すべきファイルはどれでしょう?
- 1. いきなり Battery.cpp を開く
- 2. まず SecurityRobot.h に公開関数のシグネチャを宣言し、その後 SecurityRobot.cpp に処理を実装する
- 3. main.cpp の末尾に直接グローバル関数を書く
- 4. Common.h の定数を書き換える
正解と解説を見る
正解:まず SecurityRobot.h に公開関数のシグネチャを宣言し、その後 SecurityRobot.cpp に処理を実装する
正解です!外部仕様書(ヘッダ .h)にインターフェースを定義してから実装(.cpp)を行うのがC++の正しい開発フローです。
Q4. 上のUMLクラス図の操作(メソッド)をクリックしたとき、エディタ側で起きる動作として正しいものはどれでしょう?
- 1. プログラムが自動的にコンパイルされて実行される
- 2. 該当するファイルタブへ自動で切り替わり、定義されているコード行へスムーズスクロールしてハイライト点滅する
- 3. コードが消去される
- 4. 新しいファイルが作成される
正解と解説を見る
正解:該当するファイルタブへ自動で切り替わり、定義されているコード行へスムーズスクロールしてハイライト点滅する
正解です!当サイトの「UML ⇄ ソースコード双方向連動」機能により、設計書から該当の実装コードへ瞬時にジャンプして確認できます。
🐻❄️ シロクマC++ラボ インタラクティブ学習システム
ブラウザでJavaScriptを有効にすると、ブラウザ内インベーダーゲームエミュレータ、メモリマップ可視化、UMLクラス図、対話型解説、理解度クイズが起動します。
インタラクティブ学習を開始する →