CPP04 1.0
読み取り中…
検索中…
一致する文字列を見つけられません
raii

C++98 で new 演算子などのエラーハンドリングにおいて、リソース解放を確実に行うための最も推奨される仕組みは、 RAII (Resource Acquisition Is Initialization) パターン です。

RAII は、「リソースの取得をオブジェクトの初期化と結びつけ、リソースの解放をそのオブジェクトのデストラクタと結びつける」という設計原則です。これにより、オブジェクトがスコープを抜けるとき(正常終了時でも、例外によってスタックが巻き戻される時でも)、デストラクタが自動的に呼び出され、リソースが確実に解放されます。

C++98 には std::unique_ptrstd::shared_ptr のようなモダンなスマートポインタはありませんが、 カスタムのスマートポインタ(RAII ラッパー)を自作する ことで同等の機能を実現できます。


推奨される方法: カスタムの RAII ラッパー (スマートポインタ) を実装する

  1. ScopedPointer.hpp (カスタムスマートポインタのヘッダーファイル)

    ‍このクラスは、new で単一のオブジェクトを割り当てる場合に特化した、簡易的な unique_ptr のような振る舞いをします。コピーを禁止することで、所有権の曖昧さを避けます。

    #ifndef SCOPED_POINTER_HPP
    #define SCOPED_POINTER_HPP
    #include <iostream> // デバッグ出力用 (任意)
    // テンプレートクラスとして定義
    template <typename T>
    private:
    T* m_ptr; // 管理する生ポインタ
    // コピーコンストラクタとコピー代入演算子を禁止
    // これにより、所有権のコピーを防ぎ、二重解放を回避する
    ScopedPointer& operator=(const ScopedPointer&);
    public:
    // コンストラクタ: 生ポインタを受け取り、所有権を持つ
    explicit ScopedPointer(T* ptr = 0) : m_ptr(ptr) {
    // std::cout << "ScopedPointer(" << m_ptr << ") created." << std::endl; // デバッグ用
    }
    // デストラクタ: スコープを抜けるときに管理下のメモリを解放
    // std::cout << "ScopedPointer(" << m_ptr << ") destroyed. Deleting resource." << std::endl; // デバッグ用
    delete m_ptr;
    }
    // 生ポインタへのアクセスを提供する
    T* get() const {
    return m_ptr;
    }
    // ポインタのように Dereference 演算子 (*) をオーバーロード
    T& operator*() const {
    // null ポインタの場合は未定義動作になるが、通常は呼び出し側でチェック
    return *m_ptr;
    }
    // ポインタのように Member Access 演算子 (->) をオーバーロード
    T* operator->() const {
    // null ポインタの場合は未定義動作になるが、通常は呼び出し側でチェック
    return m_ptr;
    }
    // 所有権を解放し、生ポインタを返す
    // ポインタを受け取った側が解放の責任を持つ
    T* release() {
    T* temp = m_ptr;
    m_ptr = 0; // null にする (デストラクタで delete しないように)
    return temp;
    }
    // 新しいポインタを割り当て直す(以前のポインタは解放される)
    void reset(T* ptr = 0) {
    if (m_ptr != ptr) { // 同じポインタでないかチェック
    delete m_ptr;
    m_ptr = ptr;
    }
    }
    };
    #endif // SCOPED_POINTER_HPP
    void reset(T *ptr=0)
    T * get() const
    T * operator->() const
    T & operator*() const
    2. main.cppScopedPointer を使用する

    #include <iostream>
    #include <string>
    #include <new> // std::bad_alloc のために必要
    #include <cstdlib> // EXIT_FAILURE のために必要
    // 仮のクラス定義 (Animal, Dog, Cat など)
    // 実際には、これらのクラスのヘッダーファイルをインクルードしてください
    class Animal {
    public:
    virtual ~Animal() { std::cout << "Animal destructor called." << std::endl; }
    virtual std::string getType() const { return "Animal"; }
    virtual void makeSound() const { std::cout << "Animal makes a sound." << std::endl; }
    };
    class Dog : public Animal {
    public:
    virtual ~Dog() { std::cout << "Dog destructor called." << std::endl; }
    virtual std::string getType() const { return "Dog"; }
    virtual void makeSound() const { std::cout << "Woof!" << std::endl; }
    };
    class Cat : public Animal {
    public:
    virtual ~Cat() { std::cout << "Cat destructor called." << std::endl; }
    virtual std::string getType() const { return "Cat"; }
    virtual void makeSound() const { std::cout << "Meow!" << std::endl; }
    };
    // 作成したスマートポインタのヘッダーをインクルード
    #include "ScopedPointer.hpp"
    int main()
    {
    // ポインタ変数ではなく、ScopedPointer のインスタンスをスタックに作成する
    // これらは main 関数のスコープを抜けるときに自動的に解放される
    try {
    // new で割り当てた生ポインタを ScopedPointer オブジェクトのコンストラクタに直接渡す
    // または reset() を使用する
    meta.reset(new Animal()); // A
    j.reset(new Dog()); // B
    i.reset(new Cat()); // C (ここで std::bad_alloc がスローされたと仮定)
    // もし C で例外がスローされたとしても、
    // A と B で割り当てられたメモリは、meta と j のデストラクタが
    // 自動的に呼ばれることで解放される。
    std::cout << j->getType() << " " << std::endl;
    std::cout << i->getType() << " " << std::endl;
    i->makeSound();
    j->makeSound();
    meta->makeSound();
    // ここで明示的に delete する必要はない
    // delete meta;
    // delete j;
    // delete i;
    } catch (const std::bad_alloc& e) {
    std::cerr << "Error: Memory allocation failed! " << e.what() << std::endl;
    // 例外捕捉後、ScopedPointer のデストラクタが自動で呼ばれ、
    // 既に割り当てたメモリが解放されることが保証される。
    return EXIT_FAILURE;
    } catch (const std::exception& e) {
    std::cerr << "An unexpected standard error occurred: " << e.what() << std::endl;
    return EXIT_FAILURE;
    } catch (...) {
    std::cerr << "An unknown error occurred." << std::endl;
    return EXIT_FAILURE;
    }
    // main 関数が終了するとき、meta, j, i の ScopedPointer オブジェクトがスコープを抜ける。
    // その際にそれぞれのデストラクタが呼ばれ、内部の生ポインタが指すメモリが解放される。
    return 0;
    }
    The Animal class represents a generic animal.
    Definition Animal.hpp:37
    const std::string & getType() const
    Gets the type of the animal.
    Definition Animal.cpp:72
    virtual ~Animal()
    Destructor for Animal. Displays a destruction message.
    Definition Animal.cpp:59
    virtual void makeSound() const
    Makes a generic animal sound. This implementation is for the base Animal class. Derived classes are e...
    Definition Animal.cpp:66
    The Cat class represents a feline animal.
    Definition Cat.hpp:33
    virtual void makeSound() const
    Makes the characteristic sound of a cat ("Meow!"). This function overrides the virtual makeSound() fr...
    Definition Cat.cpp:65
    virtual ~Cat()
    Destructor for Cat. Displays a specific destruction message.
    Definition Cat.cpp:45
    The Dog class represents a canine animal.
    Definition Dog.hpp:33
    virtual void makeSound() const
    Makes the characteristic sound of a dog ("Woof!"). This function overrides the virtual makeSound() fr...
    Definition Dog.cpp:65
    virtual ~Dog()
    Destructor for Dog. Displays a specific destruction message.
    Definition Dog.cpp:45
    T endl(T... args)
    int main()
    Main function of the program.
    Definition main.cpp:35
    T what(T... args)

解説

1. RAII の原則:

  • ScopedPointer クラスは RAII (Resource Acquisition Is Initialization) の原則に従います。
  • ヒープメモリ (new で割り当てたもの) は、 ScopedPointer オブジェクトが構築されるときに「取得」されます。
  • その ScopedPointer オブジェクトがスコープを抜けるとき(関数の終了時、例外によるスタック巻き戻し時など)に、自動的にデストラクタが呼び出され、delete 演算子によってヒープメモリが「解放」されます。

2. ScopedPointer の特徴:

  • コンストラクタ: new が返した生ポインタを受け取ります。
  • デストラクタ: 内部で管理している生ポインタに対して delete を呼び出します。
  • コピー禁止: 複数の ScopedPointer が同じメモリを指すと二重解放の原因になるため、コピーコンストラクタとコピー代入演算子を private にすることで、 ScopedPointer オブジェクトのコピーを禁止しています。これにより、所有権は常に単一の ScopedPointer オブジェクトに限定されます。
  • ポインタのようなアクセス: operator*()operator->() をオーバーロードしているため、 ScopedPointer オブジェクトをまるで生ポインタのように *ptrptr->member の形で使うことができます。
  • reset() メソッド: 必要に応じて、 ScopedPointer が管理するポインタを別のポインタに置き換えたり、0 にしたりできます。reset() が呼ばれると、それまで管理していたメモリは解放されます。

3. 例外安全性:

  • try ブロック内で new Cat();std::bad_alloc をスローした場合、その時点ですぐに try ブロックを抜け、対応する catch ブロックにジャンプします。
  • このとき、metajScopedPointer のオブジェクトとしてスタックに存在しているため、スコープを抜ける際にそれぞれのデストラクタが自動的に呼び出されます。
  • デストラクタが呼ばれることで、metaj が指していたメモリは正しく解放され、メモリリークは発生しません。

まとめ

C++98 で new のエラーハンドリングにおいて、メモリリークを防ぐための最も堅牢で推奨される方法は、このように RAII パターンを適用したカスタムのスマートポインタを実装することです。