C++レビュー|動的メモリ確保頻度最適化と設計レビュー観点整理
この記事のポイント
- 動的メモリ確保頻度をレビューアーがどのように読み解くか整理
- new/deleteの責務整理から確保回数抑制までレビュー指摘できる
- 性能・保守性・安全性を両立した設計へ誘導する観点を解説
そもそも動的メモリ確保最適化とは
C++における new / delete を伴う動的メモリ確保は非常に柔軟ですが、同時に以下の設計課題を内包します。
- 確保コストは高い(ヒープ管理負荷)
- 確保回数が多いほど断片化・パフォーマンス劣化
- 確保/解放の責務が設計分散しやすい
- 例外安全性への影響が大きい
そのため設計段階から「そもそも確保回数を抑制する」発想が重要となります。
最適化の基本原則
- まとめて確保、まとめて保持
- スコープ管理で寿命を統一
- 分散ではなく集中所有
- 可能な限りスマートポインタ・コンテナで管理
レビューアーはコードを読み解き、不必要に分散した確保呼び出しを発見し整理提案することが役割となります。
なぜこれをレビューするのか
確保頻度が最適化されないと以下の設計崩壊が起きます。
- パフォーマンス劣化(頻繁なヒープ操作)
- キャッシュミス増加
- メモリ断片化
- 保守性劣化(確保・解放箇所が増殖)
レビュー段階で早期に「new頻度を設計的に減らせる構造か?」を評価する習慣が極めて重要です。
レビューアー視点
- 毎ループ・毎関数呼び出しでnewしていないか
- 使い回し・事前確保構造に設計変更できないか
- スマートポインタや標準コンテナ移行可能性を確認
- 所有権集中構造を誘導
- 寿命と責務を一致させる設計に誘導
開発者視点
- 極力new/deleteを使わない
- スマートポインタ・vector初期確保で管理
- 再利用性重視のバッファ設計を活用
- スコープ終了と解放タイミング一致を徹底
良い実装例
想定:APIリクエストログの大量記録処理
良い設計例
#include <vector>
#include <string>
#include <iostream>
struct ApiRequestLog {
int requestId;
std::string endpoint;
std::string clientIp;
int responseCode;
time_t requestedAt;
};
class LogBuffer {
public:
explicit LogBuffer(size_t capacity) {
logs_.reserve(capacity); // 事前確保による確保回数削減
}
void add(const ApiRequestLog& entry) {
logs_.push_back(entry);
}
void dump() const {
for (const auto& log : logs_) {
std::cout << log.requestId << std::endl;
}
}
private:
std::vector<ApiRequestLog> logs_;
};良いポイント
- reserveで確保頻度を削減
- 動的メモリ確保の分散排除
- 例外安全性確保済み
- 確保コストと寿命責務を集中
レビュー観点
- 毎ループnewが発生していないか
- 事前reserve・一括確保構造が設計可能か
- スマートポインタ・vectorで寿命集中できているか
- new/delete責務が分散していないか
- API契約も所有権整理されているか
- 例外安全性が保証されているか
良くない実装例: ケース1(毎回new発生型)
問題例①
void process() {
for (int i = 0; i < 1000; ++i) {
ApiRequestLog* log = new ApiRequestLog();
log->requestId = i;
// 処理略
delete log;
}
}
@Reviewer毎回new/deleteを繰り返す設計は非効率です。スコープ配列管理またはvectorで一括管理し、確保頻度を大幅に削減してください。
問題点
- 確保呼び出しコスト肥大化
- delete漏れリスク
- ヒープ断片化助長
改善例
改善例①
std::vector<ApiRequestLog> logs;
logs.reserve(1000);
for (int i = 0; i < 1000; ++i) {
ApiRequestLog log{i, "/api", "127.0.0.1", 200, std::time(nullptr)};
logs.push_back(log);
}良くない実装例: ケース2(複数クラス分散確保)
問題例②
class Session {
public:
void start() {
log_ = new ApiRequestLog();
}
void end() {
delete log_;
}
private:
ApiRequestLog* log_;
};
@Reviewernew/delete責務がクラス外部へ分散しやすくなります。スマートポインタ保持でスコープ管理に移行してください。
問題点
- 寿命と責任の分離
- 例外安全性崩壊
- 所有権読解困難
改善例
改善例②
class Session {
public:
void start() {
log_ = std::make_unique<ApiRequestLog>();
}
private:
std::unique_ptr<ApiRequestLog> log_;
};良くない実装例: ケース3(リサイズ頻発型)
問題例③
std::vector<ApiRequestLog> logs;
for (int i = 0; i < 10000; ++i) {
ApiRequestLog log{i, "/api", "127.0.0.1", 200, std::time(nullptr)};
logs.push_back(log);
}
@Reviewer事前にreserve()で確保量を先に割当ててください。リサイズ頻発により確保コストが増加しています。
改善例
改善例③
std::vector<ApiRequestLog> logs;
logs.reserve(10000);
for (int i = 0; i < 10000; ++i) {
ApiRequestLog log{i, "/api", "127.0.0.1", 200, std::time(nullptr)};
logs.push_back(log);
}観点チェックリスト
まとめ
動的メモリ確保最適化レビューは
「そもそも確保責務を散らさない設計」を作るレビューです。
レビューアーは常に
- new呼び出し頻度は抑えられているか?
- 確保・保持・破棄が同一スコープ集中か?
- 事前確保設計が検討されているか?
を読み解き、RAII徹底+ヒープ効率化設計へ誘導する判断力が重要になります。
