ikautak.log

C/C++, Python, CUDA, Android, Linux kernel, Network, etc.

More Effective C++ 項目10 コンストラクタでのリソースリークを防ぐ

新訂版 More Effective C++ (AddisonーWesley professional co)

新訂版 More Effective C++ (AddisonーWesley professional co)

  • 作者: スコット・メイヤーズ,安村通晃,伊賀聡一郎,飯田朱美,永田周一
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/06/29
  • メディア: 単行本(ソフトカバー)
  • 購入: 8人 クリック: 129回
  • この商品を含むブログ (43件) を見る

項目10 コンストラクタでのリソースリークを防ぐ

顔写真や音声も保存できる住所録を考える。

// 画像
class Image {
public:
    Image(const string& imageDataFileName);
    ...
};

// 音声データ
class AudioClip {
public:
    AudioClip(const string& audioDataFileName);
    ...
};

// 住所録
class BookEntry {
public:
    BookEntry(const string& name,
        const string& address = "",
        const string& imageFileName = "",
        const string& audioClipFileName = "");
    ~BookEntry();
    ...

private:
    string theName;             // 名前
    string theAddress;          // 住所
    list<PhoneNumber> thePhone; // 電話番号
    Image* theImage;            // 画像
    AudioClip* theAudioClip;    // 音声クリップ
};

名前は必須だとする。コンストラクタは以下のようになる。

BookEntry::BookEntry(const string& name,
    const string& address,
    const string& imageFileName,
    const string& audioClipFileName)
: theName(name), theAddress(address),
  theImage(0), theAudioClip(0)      // nullに初期化
{
    if (imageFileName != "") {
        theImage = new Image(imageFileName);
    }
    if (audioClipFileName != "") {
        theAudioClip = new AudioClip(audioClipFileName); // 例外が発生するとtheImageは
                                                         // 破棄されない
    }
}

BookEntry::~BookEntry() {
    delete theImage;
    delete theAudioClip;
}

コンストラクタのAudioClipオブジェクト作成部分で、メモリが足りなくて例外が起きた場合、theImageオブジェクトは破棄されない。
コンストラクタが終了していないオブジェクトはデストラクタが呼ばれない。

例外が発生した場合に、自分でdeleteを読んでも無駄だ。

BookEntry* pb = 0;
try {
    pb = new BookEntry("addison", "one jacob way");
    ...
} catch (...) {
    delete pb; // newに失敗していたら、pbはnullのままなので、
               // 意味がない
    throw;
}

生成中に例外が発生したオブジェクトの後片付けをC++はやらない。
コンストラクタを設計する際は後片付けを考える必要がある。

BookEntry::BookEntry(const string& name,
    const string& address,
    const string& imageFileName,
    const string& audioClipFileName)
: theName(name), theAddress(address),
  theImage(0), theAudioClip(0)
{
    try {
        if (imageFileName != "") {
            theImage = new Image(imageFileName);
        }
        if (audioClipFileName != "") {
            theAudioClip = new AudioClip(audioClipFileName);
        }
    } catch (...) {          // どんな例外も受け取る

        delete theImage;     // 例外発生時に後片付けを
        delete theAudioClip; // 実行する

        throw;               // 例外を伝搬
    }
}

auto_ptrを使うことで、ポインタ変数がなくなるときに、指し示すオブジェクトも後片付けをすることができる。

class BookEntry {
public:
    ...

private:
    ...
    const auto_ptr<Image> theImage;          // 今度は
    const auto_ptr<AudioClip> theAudioClip;  // auto_ptrにする
};

これでコンストラクタで例外が発生してもリソースリークは起こらない。
theImageとtheAudioClipは初期化リストでの初期化もできるようなる。

BookEntry::BookEntry(const string& name,
    const string& address,
    const string& imageFileName,
    const string& audioClipFileName)
: theName(name), theAddress(address),
  theImage(imageFileName != ""
    ? new Image(ImageFileName)
    : 0),
  theAudioClip(audioClipFileName != ""  // 例外が発生してもtheImageは破棄される
    ? new AudioClip(audioClipFileName)
    : 0)
{}

theImageとtheAudioClipはオブジェクトなので、BookEntryオブジェクトが破棄されるときには自動で破棄される。
よってデストラクタは空になる。

BookEntry::~BookEntry()
{}                      // 何もしなくてよい

まとめ
クラスのポインタメンバをauto_ptrオブジェクトにすることで、例外が発生した場合でもリソースリークを防ぐことができ、デストラクタで手動で破棄する必要もなくなる。