shared_ptr にヒープ以外のものを掴ませたいなら何もしない Deleter も一緒に指定してあげればいい このエントリーをはてなブックマークに追加

これまた非常に初歩的な話なのですが…

  • STL のコンテナ (std::vector とか) には基本的にオブジェクトへの参照を掴ませることはできない (領域確保の効率の問題とかでデフォルトコンストラクタが必須だから)。
  • かといって、ナマポを掴ませるのは色々と危険 (意図せず寿命が切れて無効なポインタ掴まされた状態になったりとか)。
  • そも non-copyable なオブジェクトを扱いたかったりと言った理由で値そのものを掴ませるのは却下したい (まぁ、いろいろあるんだよ…)。
  • となると std::shared_ptr を噛ますのが順当かと思いきや…
  • 必ずしもヒープに確保した (つまり、 new した) インスタンスだけを扱うとは限らないかもしれない。十分に寿命が長いことがわかっている自動変数やメンバ変数として確保したインスタンスのポインタをこのコンテナに突っ込んであげたい場合もあるかもしれない。
  • そういう場合に std::shared_ptr 使っちゃって大丈夫なのか…??

Deleter を指定してあげれば大丈夫

std::shared_ptr の Deleter って、 Win32 の HANDLE みたいに delete 以外の方法で開放するリソースを扱う方法として紹介されることが多いと思うんだけど、単純に、リソース開放の責任はここにはないよ、ということを明示する方法としてももちろん使えるよという、ただそれだけの話なんですね。

#include <memory>
#include <vector>
#include <iostream>

class Hoge {
    int number;
public:
    explicit Hoge(int num) : number{num}
    { std::cout << "Hoge " << number << " constructed." << std::endl; }
    ~Hoge() { std::cout << "Hoge " << number << " destructed." << std::endl; }
};

int main()
{
    std::vector<std::shared_ptr<Hoge>> hogehoge;
    hogehoge.push_back(std::shared_ptr<Hoge>{new Hoge(1)});    // まぁ普通…
    Hoge fuga{2};
    hogehoge.push_back(std::shared_ptr<Hoge>{&fuga, [](Hoge *){}});    // を、行けるやん!!
    Hoge piyo{3};
    hogehoge.push_back(std::shared_ptr<Hoge>{&piyo});    //(アカン)

    return 0;
}

実行結果:

murachi@erueru:~/github/samples-for-blog/c++/minimal-cases/shared-pointer-deleter$ g++ -std=c++11 -o hoge hoge.cpp
murachi@erueru:~/github/samples-for-blog/c++/minimal-cases/shared-pointer-deleter$ ./hoge
Hoge 1 constructed.
Hoge 2 constructed.
Hoge 3 constructed.
Hoge 3 destructed.
Hoge 2 destructed.
Hoge 1 destructed.
Hoge 3 destructed.
*** Error in `./hoge': double free or corruption (out): 0x00007fff8a6192a0 ***
中止 (コアダンプ)
murachi@erueru:~/github/samples-for-blog/c++/minimal-cases/shared-pointer-deleter$ 

何もしない Deleter を指定しなかった場合、 std::vector が開放される際に std::shared_ptrdelete を呼ぼうとするんだけど、出自が自動変数だった当該インスタンスはスコープ抜けるときに vector より先に破棄されちゃっていたので、結果コアダンプしてしまうわけですが、 Deleter を指定してあげた方は std::vector 開放時には何もしないので、結果開放処理が重複することなく済むわけです。 Deleter が lambda で書けちゃうのもいいですね。

ただ、この方法は std::shared_ptr でメモリー管理する領域よりも、生成元での寿命のほうが確実に長いことがわかっている場合以外は使えません。そういう意味では、プログラマーがメモリーの管理責任から開放されるための仕組みとして std::shared_ptr やガーベジコレクションのようなものを採用していることとは矛盾するやり方だと言えるでしょう。基本的には、 std::shared_ptr で扱うべきものは、プログラム全体で std::shared_ptr を介してのみ扱うよう徹底するのが正しいやり方なのかもしれません。

2014 年 7 月 27 日 by 村山 俊之

タグ: ,

コメントをどうぞ