メモリ所有権

誰がメモリを破棄するのか。設計の基本。

概要

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;
}

所有権の管理方法

明確な規約を決めることが重要です:

  1. 呼び出し元が所有:関数はポインタを受け取るのみ
  2. 関数が所有:関数がメモリを作成・破棄
  3. スマートポインタで管理:自動破棄

ベストプラクティス:スマートポインタ

Modern C++では、std::unique_ptrstd::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で安全運用