auto_ptrを使いまわす時のデストラクタの順番

auto_ptrは便利である。単にメモリリークを防止する手段に留まらず色々な場面で我々を助けてくれる。
例えばオブジェクトの実体を確保するポインタの場合。中身が入ってないことを示すために初期化でNULLを入れておいたり、中身を開放した後もNULLを入れたり、そんな面倒なことしなくてもauto_ptrの場合、中身が無い状態の時は自動的にNULLを入れておいてくれるのである。気が利くメイドさんみたいで嬉しい。

もちろんメイン機能である自動開放もエラー処理や例外で関数を抜けなければならないときにオブジェクトの後始末を考えなくてなくて済むので助かる。気が利くメイドさんみたいで……(それはもういいって)

だが、auto_prtの意味が所有権をもつポインタということを考えると単なる自動管理として考えていると微妙に痛い目にあうこともある。いや、痛い目に会ったのは私だけかもしれないが。


// テスト用クラス
class Test
{
public:
    Test(string n){
        name_ = n;
        cout << name_ << " コンストラクタ\n";
        }
    ~Test(){
        cout << name_ << " デストラクタ\n";
    }
private:
    string name_;
};


int main()
{
    auto_ptr<Test> test;
    test = auto_ptr<Test>(new Test("オブジェクト1"));

    // 現在オブジェクト1の時代

    test = auto_ptr<Test>(new Test("オブジェクト2"));

    // 現在オブジェクト2の時代

    return 0;
}

上記のコードを実行してみると結果はこうなる。

【実行結果】
オブジェクト1 コンストラクタ   ←(オブジェクト1作成時)
オブジェクト2 コンストラクタ   ←(オブジェクト2作成時)
オブジェクト1 デストラクタ    ←(auto_ptrの所有権が移動した時に呼ばれたデストラクタ)
オブジェクト2 デストラクタ    ←(auto_ptrのスコープアウトによるデストラクタ)

オブジェクト1が存在する時に新しくオブジェクト2を生成して交代させるというコードのつもりなのだが、問題になるのは二つのオブジェクトのコンストラクタ、デストラクタの順番だ。新しいオブジェクトが生成した後に古いオブジェクトのデストラクタが呼ばれている。インスタンスが二つ存在する瞬間があるのだ。
まあ声を大きくして主張しなくても別にふつーの動作なのであるが。

実際私があった痛い目というのは。
ある日ネット系のゲームにてキャラクターのステータスを表示するクラスを書いた。そのクラスはコンストラクタで通信経路をオープンしてデストラクタでクローズするような仕様だった。ちなみに今回の通信プロトコルで一度にオープンできる通信経路は一つだけである。
だが、実際にテストしてみると最初に表示したキャラクターのステータスは普通に表示されるのに二人目以降は全く表示されない現象が発生した。
ここまで読んだ賢明な読者にはauto_ptrで管理されたそのオブジェクトがどんな動作をしたか想像できるだろう。

キャラ1 通信オープン
キャラ2 通信オープン
キャラ1 通信クローズ
キャラ2 通信クローズ
おいおい通信は一つしかオープンできないのに二つオープンしちゃってるよ。しかもその後丁寧にクローズしちゃってるし。 これじゃ二人目以降のステータスが表示されるわけはないって。
このような現象が起きないように修正を加えるとしたら二つ目のオブジェクトを作成する前に前のオブジェクトを手動で消してしまえばよいだろう。
    test.reset(); // 削除(※1)
    test = auto_ptr<Test>(new Test("オブジェクト2"));
こんな感じ。動作もきっちり
キャラ1 通信オープン
キャラ1 通信クローズ
キャラ2 通信オープン
キャラ2 通信クローズ

の順番になると思う。


※1 VC6のような古いコンパイラの場合reset()メンバが無いので以下のように空の要素を入れて削除する方法がある。
test = auto_ptr<Test>();
reset()についてはc++標準化委員会における1996〜1998年の3回にわたる改定で色々あった挙句追加されたようだが、VC6が発売されたのが丁度この頃なので使えなかったようだ。他にもVC6のc++は今の仕様からみると実装が怪しい部分がある。


戻る