メモリ所有権
誰がメモリを破棄するのか。設計の基本。
概要
C++では、who owns the memory?が重要です。 メモリを所有する者が破棄する責任を持ちます。
これが曖昧なと、メモリリークやダブルデリートが起こります。
所有権の明示
例:呼び出し元が所有する場合
#include <iostream>
// 呼び出し元が所有権を持つ
void processArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int* data = new int[5]{1, 2, 3, 4, 5};
processArray(data, 5); // 呼び出し元が所有
delete[] data; // 呼び出し元が破棄
data = nullptr;
return 0;
}
例:関数が所有権を譲渡する場合
#include <iostream>
// 関数が新しいメモリを作成し所有権を譲渡
int* createArray(int size) {
int* arr = new int[size];
return arr; // 所有権は呼び出し元へ
}
int main() {
int* data = createArray(5); // 呼び出し元が所有権を獲得
// ... 使用 ...
delete[] data; // 呼び出し元が破棄する責任
data = nullptr;
return 0;
}
所有権の曖昧性がバグを生む
反例1: メモリリーク
void badExample() {
int* data = new int[100];
// 誰が破棄するか不明確
// delete[] data; // 破棄を忘れてしまう
}
int main() {
badExample(); // メモリリーク発生
return 0;
}
反例2: ダブルデリート
void deleteResource(int* ptr) {
delete[] ptr; // 関数側で破棄
}
int main() {
int* data = new int[100];
deleteResource(data);
delete[] data; // エラー! ダブルデリート
return 0;
}
所有権の管理方法
明確な規約を決めることが重要です:
- 呼び出し元が所有:関数はポインタを受け取るのみ
- 関数が所有:関数がメモリを作成・破棄
- スマートポインタで管理:自動破棄
ベストプラクティス:スマートポインタ
Modern C++では、std::unique_ptrと std::shared_ptrで所有権を明示できます。
#include <iostream>
#include <memory>
// unique_ptr: 単独所有
std::unique_ptr<int> createValue() {
return std::make_unique<int>(42);
}
// shared_ptr: 複数所有
std::shared_ptr<int> shareValue() {
return std::make_shared<int>(42);
}
int main() {
{
auto val = createValue(); // 所有開始
std::cout << *val << std::endl;
} // 自動破棄
{
auto val1 = shareValue(); // 共有開始
auto val2 = val1; // 参照カウント +1
} // 両方破棄時に自動削除
return 0;
}
42
ポイント
- メモリ所有権を明確に
- 所有者が破棄する責任
- スマートポインタで自動管理
- 所有権曖昧性がバグの元
まとめ
- who owns?を常に意識
- Manual new/deleteは所有権明示が必須
- std::unique_ptr/shared_ptrで安全運用