C++レビュー|オブジェクト寿命(lifetime)管理の明示と設計レビュー観点整理
この記事のポイント
- オブジェクト寿命(lifetime)設計をレビューアーが読み解く技術を整理
- 寿命責務をコードから読み取るレビュー観点を明確化
- 所有権・スコープ・例外安全性を統合してレビューできる
そもそもオブジェクト寿命(lifetime)とは
C++におけるオブジェクト寿命とは、
オブジェクトが生成され、破棄されるまでのスコープ・期間そのものを指します。
void process() {
ApiRequestLog log; // 生成
// 利用中
} // ここで破棄寿命管理を適切に設計できないと以下の問題が発生します。
- 早すぎる破棄 → ダングリングポインタ
- 遅すぎる破棄 → メモリリーク
- 二重解放 → 未定義動作
C++の設計では寿命管理の明示が非常に重要です。
寿命明示設計の基本原則
- 所有権保持者が寿命責任を持つ
- スコープ管理を寿命設計の基盤に置く
- RAII・スマートポインタで寿命を埋め込む
レビューアーは「このオブジェクトの寿命は誰が管理しているのか?」を常に読み取ります。
なぜこれをレビューするのか
寿命管理が不明瞭だと以下の設計事故が起きます。
- 解放忘れによるメモリリーク
- use-after-freeバグ
- スレッド競合による破棄時不整合
- 例外時のリーク発生
レビュー段階で寿命管理責務が整理されているか確認することは
設計の安全性と保守性を決定付ける重大観点です。
レビューアー視点
- 誰が所有権を持ち寿命を管理しているか判読
- スマートポインタ・スコープ構造が寿命を保証しているか確認
- API契約が寿命を隠蔽しているか確認
- 例外時の途中破棄が保証されているか確認
開発者視点
- スコープ寿命に極力合わせる
- 所有権移譲時は明示的moveで表現
- shared_ptr使用時は共有責任範囲を厳密に限定
- new/deleteは封じ込め、RAII徹底
良い実装例
想定:APIリクエストログの寿命集中モデル
良い設計例
#include <memory>
#include <string>
#include <iostream>
struct ApiRequestLog {
int requestId;
std::string endpoint;
std::string clientIp;
int responseCode;
time_t requestedAt;
};
class LogManager {
public:
void save(std::unique_ptr<ApiRequestLog> logEntry) {
logs_.push_back(std::move(logEntry));
}
void dump() const {
for (const auto& entry : logs_) {
std::cout << entry->requestId << std::endl;
}
}
private:
std::vector<std::unique_ptr<ApiRequestLog>> logs_;
};
void process() {
auto log = std::make_unique<ApiRequestLog>();
log->requestId = 123;
LogManager manager;
manager.save(std::move(log));
}良いポイント
- 呼び出し側で生成 → 明示移譲(move) → 保持
- 寿命責任が所有権移譲で自然に読める
- new/delete露出排除
- スコープ終了で自動解放
レビュー観点
- 寿命管理責務がコード上に明示されているか
- 所有権移譲時はmove表現されているか
- new/delete露出が排除されているか
- 例外発生時も寿命管理が保証されているか
- API契約が寿命管理を隠蔽してくれているか
- スマートポインタ・RAIIで寿命設計が埋め込まれているか
良くない実装例: ケース1(寿命責務の分散)
問題例①
class LogManager {
public:
void save(ApiRequestLog* logEntry) {
logs_.push_back(logEntry);
}
void cleanup() {
for (auto* entry : logs_) {
delete entry;
}
logs_.clear();
}
private:
std::vector<ApiRequestLog*> logs_;
};
@Reviewer寿命責務が呼び出し側・クラス内部で分散しています。unique_ptrを使い、所有権移譲を設計に埋め込んでください。cleanupも不要になります。
問題点
- delete責務分散
- 呼び出し側にdelete忘れリスク
- 例外安全崩壊
改善例
改善例①
void save(std::unique_ptr<ApiRequestLog> logEntry) {
logs_.push_back(std::move(logEntry));
}良くない実装例: ケース2(寿命露出型API契約)
問題例②
void save(ApiRequestLog* logEntry);
@ReviewerAPI契約として寿命が曖昧です。所有権移譲意図ならunique_ptr受取へ変更してください。
改善例
改善例②
void save(std::unique_ptr<ApiRequestLog> logEntry);良くない実装例: ケース3(共有責務肥大化)
問題例③
class LogManager {
public:
void save(std::shared_ptr<ApiRequestLog> logEntry) {
logs_.push_back(logEntry);
}
private:
std::vector<std::shared_ptr<ApiRequestLog>> logs_;
};
@Reviewer共有所有が必要ない責務までshared_ptrで設計しないでください。寿命責任が読みにくくなります。unique_ptr管理に移行してください。
改善例
改善例③
void save(std::unique_ptr<ApiRequestLog> logEntry);観点チェックリスト
まとめ
寿命管理レビューは
「破棄責任が設計から読めるレビュー」です。
レビューアーは常に
- 誰が寿命責任を持っているか?
- 移譲は明示的に設計されているか?
- 例外時でも寿命は安全か?
を読み解き、所有権明示 → 寿命責務集中設計に誘導するのが最大の役割です。
