C++レビュー|copy_if, remove_ifの活用設計とレビュー責任整理
この記事のポイント
- copy_if, remove_ifの設計責任をレビューアー視点で整理
- 破壊型/非破壊型フィルタリングの責任分離を読み解く
- API契約・状態管理・副作用責任のレビュー観点を体系化
そもそもcopy_if, remove_ifとは
C++標準ライブラリalgorithmの中でも、特に要素選別責任を分離する代表がこれです。
remove_if
- 条件に一致する要素を「前詰め」る
- 実際の削除は行わない(eraseと組み合わせ)
auto it = std::remove_if(vec.begin(), vec.end(), pred);
vec.erase(it, vec.end());copy_if
- 条件に一致する要素のみを他コンテナにコピー
- 破壊せず抽出
std::copy_if(source.begin(), source.end(), target.begin(), pred);- remove_if:破壊前処理
- copy_if:破壊せず抽出処理
なぜこれをレビューするのか
レビューアー視点
copy_if / remove_if適用設計には以下の責任整理が必要。
-
破壊責任と保持責任の分離
→ API契約でどちらが保持管理を担当するか明確に整理 -
フィルタロジックの安定化責任
→ predicateの設計妥当性確認 -
副作用排除責任
→ イテレータ無効化/副作用埋没を未然防止 -
スケーラビリティ保証責任
→ 件数規模に応じたcopy/remove適用妥当性確認 -
API契約明文化責任
→ 破壊系/抽出系の契約分離確認
開発者視点
- remove_ifとeraseを分離実装し忘れる
- copy_if適用場面を見落とす
- predicateを状態依存にしてしまう
- 大規模破壊と大規模コピーの負荷を誤認
- 破壊責任と契約責任の切り分け意識が希薄
レビューアーはこれら「責任混濁設計」をレビュー時に発見する技術を鍛えます。
良い実装例(remove_if)
ユースケース:サーバーエラーのみ削除
#include <vector>
#include <algorithm>
struct ApiRequestLog {
int responseCode;
};
void removeServerErrors(std::vector<ApiRequestLog>& logs) {
auto it = std::remove_if(logs.begin(), logs.end(),
[](const ApiRequestLog& log) {
return log.responseCode >= 500;
});
logs.erase(it, logs.end());
}- 破壊系APIとして契約整理
- remove_if→eraseの正統構成
- 副作用ゼロ
良い実装例(copy_if)
ユースケース:成功レスポンスのみ抽出
std::vector<ApiRequestLog> extractSuccess(const std::vector<ApiRequestLog>& logs) {
std::vector<ApiRequestLog> filtered;
std::copy_if(logs.begin(), logs.end(), std::back_inserter(filtered),
[](const ApiRequestLog& log) {
return log.responseCode >= 200 && log.responseCode < 300;
});
return filtered;
}- 非破壊系API契約
- フィルタリング専用責任整理
- APIが状態維持設計
レビュー観点
- 破壊系APIと抽出系APIが分離設計されているか
- predicate責任が状態依存していないか
- 副作用がラムダ内に埋没していないか
- イテレータ失効管理が責任内包されているか
良くない実装例: ケース1
以下はremove_if後にerase統合を忘却している例。
std::remove_if(logs.begin(), logs.end(), pred);
@Reviewerremove_ifは前詰め操作のみです。erase統合で領域削除を責任保持してください。
問題点
- コンテナ末尾にゴミ要素残留
- size()が期待値と不一致
改善例
logs.erase(std::remove_if(logs.begin(), logs.end(), pred), logs.end());良くない実装例: ケース2
次はcopy_if適用場面で自前ループを維持してしまった例。
std::vector<ApiRequestLog> filtered;
for (const auto& log : logs) {
if (pred(log)) {
filtered.push_back(log);
}
}
@Reviewercopy_if適用でフィルタ責任明文化が可能です。
問題点
- 意図が読みにくい
- predicate責任が埋没
改善例
std::copy_if(...);良くない実装例: ケース3
次はラムダ内で副作用を埋め込んでしまった例。
int counter = 0;
auto it = std::remove_if(logs.begin(), logs.end(),
[&counter](const ApiRequestLog& log) {
if (log.responseCode >= 500) {
counter++;
return true;
}
return false;
});
logs.erase(it, logs.end());
@Reviewerremove_ifは副作用排除が原則です。件数集計は事前集計責任として分離設計してください。
問題点
- 状態依存副作用設計
- ラムダ純粋性崩壊
改善例
int count = std::count_if(logs.begin(), logs.end(),
[](const ApiRequestLog& log) {
return log.responseCode >= 500;
});
auto it = std::remove_if(...);
logs.erase(it, logs.end());API契約整理パターン
パターンA:破壊系API契約
void removeInvalidRequests(std::vector<ApiRequestLog>& logs);- 呼出側責任 → 破壊を許容する
- ストレージ効率重視
パターンB:抽出系API契約
std::vector<ApiRequestLog> extractSuccessRequests(const std::vector<ApiRequestLog>& logs);- 呼出側責任 → 状態を壊さない
- 複数条件抽出用途に有効
copy_if / remove_if適用早見表
| 条件 | 推奨選択 |
|---|---|
| 元コレクション破壊許容 | remove_if+erase |
| 状態維持で新規抽出 | copy_if |
| 状態混濁懸念大 | copy_if優先 |
| サイズ縮小最優先 | remove_if+erase |
PlantUMLで設計責任整理
観点チェックリスト
実務レビューFAQ
Q1. remove_ifだけで削除完了?
→ erase統合が必須。remove_if単体は前詰め処理のみ。
Q2. copy_ifはいつ積極利用?
→ 状態維持が要件に含まれる場合は優先設計。
Q3. 副作用は絶対NG?
→ ラムダ純粋関数維持が設計原則。状態集計は分離責任が安全。
Q4. API契約で破壊系/非破壊系分離は必須?
→ 設計安定性・汎用性を高める最重要設計文化。
Q5. remove_ifはerase前提文化を徹底?
→ 組織コーディング標準化を推奨。レビュー負荷が激減。
まとめ
copy_if / remove_ifはフィルタリング設計責任そのものである。
レビューアーは
- 破壊責任整理
- 状態責任整理
- 副作用排除責任
- API契約昇格設計
を読み解き、「操作ではなく責任でAPIを設計する」技術を養う必要がある。
レビューアーがフィルタリング責任整理レビューに強くなると設計品質は一段上がる。
