C++レビュー|deleteとdelete[]の管理一元化とメモリ解放責務設計レビュー
この記事のポイント
- delete/delete[]の責務分離設計をレビューアー視点で整理
- 手動deleteの分散設計をレビュー段階で摘み取れる
- スマートポインタ移行設計レビューの指導技術が学べる
そもそもdeleteとdelete[]とは
C++における動的メモリ管理で登場するのが以下の解放操作です。
delete
→ 単一オブジェクトを解放delete[]
→ 配列オブジェクトを解放
どちらもヒープリソースの解放責任を担います。
newとdeleteは必ずペアとなるべきですが、実務では以下のような崩れたコードが多発します。
Foo* ptr = new Foo(); // 単一
delete ptr;
Foo* arr = new Foo[10]; // 配列
delete[] arr;
RAII設計未導入のコードほどdelete責務が散乱しがちです。
レビューアーは「解放責任がコード内に分散していないか」を読み解く必要があります。
なぜこれをレビューするのか
delete責務がコード内で分散すると以下の設計事故を引き起こします。
- 二重delete
- delete忘れ(リーク)
- delete[]/delete混用ミス(未定義動作)
- 例外時の途中リソース破棄漏れ
レビュー段階でdelete管理構造を一元化させることが
設計の保守性・例外安全性を決定付ける重要観点になります。
レビューアー視点
- delete位置が複数箇所に分散していないか
- 配列用delete[]とdeleteが混在していないか
- 所有権がスマートポインタ化できる構造か
- コンストラクタ集中破棄モデルに寄せられるか
- 例外発生時の自動解放が担保できているか
開発者視点
- delete責務は1クラスに集約
- コンストラクタ→デストラクタ集中設計
- スマートポインタ化を優先検討
- 配列new/delete[]は極力避ける
良い実装例
想定:APIログレコードの所有権集中モデル
良い設計例
struct ApiRequestLog {
int requestId;
std::string endpoint;
std::string clientIp;
int responseCode;
time_t requestedAt;
};
class LogBuffer {
public:
LogBuffer(size_t capacity)
: size_(capacity), logs_(new ApiRequestLog[capacity]) {}
~LogBuffer() {
delete[] logs_;
}
ApiRequestLog& operator[](size_t index) {
return logs_[index];
}
private:
size_t size_;
ApiRequestLog* logs_;
};
良いポイント
- 配列delete[]責務をクラス内に集中
- 呼び出し側は解放責任を持たない
- 例外安全性が高い構造
レビュー観点
- delete/delete[]責務が1クラスに集約されているか
- 呼び出し側にdelete責務を転嫁していないか
- スマートポインタ導入余地が残されていないか
- 例外発生時の途中解放保証があるか
- 配列管理をstd::vector等へ移行検討されているか
良くない実装例: ケース1(delete責務分散)
問題例①
class LogBuffer {
public:
ApiRequestLog* create(size_t capacity) {
return new ApiRequestLog[capacity];
}
void release(ApiRequestLog* logs) {
delete[] logs;
}
};
void handleRequest() {
LogBuffer buffer;
auto logs = buffer.create(10);
// 中略
buffer.release(logs);
}
@Reviewercreate/releaseモデルは責務分散を招きます。コンストラクタで配列確保し、デストラクタでdelete[]を呼び出すRAII設計に統一してください。
問題点
- 呼び出し側にdelete責務転嫁
- 例外安全性を損なう
- API契約の複雑化
改善例
改善例①
class LogBuffer {
public:
LogBuffer(size_t capacity)
: size_(capacity), logs_(new ApiRequestLog[capacity]) {}
~LogBuffer() {
delete[] logs_;
}
};
良くない実装例: ケース2(delete/delete[]混用)
問題例②
ApiRequestLog* logs = new ApiRequestLog[10];
// 中略
delete logs; // delete[]忘れ
@Reviewer配列確保時はdelete[]を使用してください。もしくはvectorやunique_ptr配列管理へ移行し、解放責務を構造的に排除してください。
問題点
- 配列をdeleteで解放 → 未定義動作
- 静的解析で拾いにくい実害バグ発生
改善例
改善例②
delete[] logs;
もしくは完全自動化:
改善例②(推奨)
std::vector<ApiRequestLog> logs(10);
良くない実装例: ケース3(スマートポインタ未活用)
問題例③
ApiRequestLog* logs = new ApiRequestLog[10];
// 中略
delete[] logs;
@Reviewernew/delete[]は可能な限り排除し、std::vectorまたはstd::unique_ptr<T[]>での自動管理に移行してください。RAII設計に統合しましょう。
改善例
改善例③(vector移行)
std::vector<ApiRequestLog> logs(10);
または
改善例③(unique_ptr配列)
std::unique_ptr<ApiRequestLog[]> logs(new ApiRequestLog[10]);
観点チェックリスト
まとめ
delete管理レビューは責務集中レビューそのものです。
レビューアーは常に
- 解放責任が1クラスに閉じ込められているか?
- 呼び出し側に解放責任を転嫁していないか?
- new/deleteの露出が残っていないか?
を静的に読み解き、RAII構造への寄せ方を指導していくことが重要です。