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_ptr
が delete
を呼ぼうとするんだけど、出自が自動変数だった当該インスタンスはスコープ抜けるときに vector より先に破棄されちゃっていたので、結果コアダンプしてしまうわけですが、 Deleter を指定してあげた方は std::vector
開放時には何もしないので、結果開放処理が重複することなく済むわけです。 Deleter が lambda で書けちゃうのもいいですね。
ただ、この方法は std::shared_ptr
でメモリー管理する領域よりも、生成元での寿命のほうが確実に長いことがわかっている場合以外は使えません。そういう意味では、プログラマーがメモリーの管理責任から開放されるための仕組みとして std::shared_ptr
やガーベジコレクションのようなものを採用していることとは矛盾するやり方だと言えるでしょう。基本的には、 std::shared_ptr
で扱うべきものは、プログラム全体で std::shared_ptr
を介してのみ扱うよう徹底するのが正しいやり方なのかもしれません。
2014 年 7 月 27 日 by 村山 俊之