タイトルのとおりです。
基本的な使用方法
まず基本的な使い方。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { { std::shared_ptr<Hoge> hoge1(new Hoge); // 初期化 hoge1->number_ = 30; { std::shared_ptr<Hoge> hoge2 = hoge1; std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; std::cout << "hoge2->number_ : " << hoge2->number_ << std::endl; } // ここでhoge2はスコープから抜けるけどhoge1はまだ生きてる std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; } // ここでhoge1もスコープから抜けるためHogeのデストラクタが呼ばれる return 0; }
実行結果は以下
hoge1->number_ : 30 hoge2->number_ : 30 hoge1->number_ : 30 Hogeのデストラクタだよ
あるオブジェクトを指し示すポインタがすべてなくなるとそのオブジェクトのデストラクタを呼んでくれるってことみたいです。
初期化の仕方以外は普通のポインタのように扱えます。
当たり前ですがshared_ptrを使わないとメモリリークが発生しますね。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { { Hoge* hoge1= new Hoge; hoge1->number_ = 30; { Hoge* hoge2 = hoge1; std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; std::cout << "hoge2->number_ : " << hoge2->number_ << std::endl; } std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; } // ここでhoge1がスコープを抜けるけどデストラクタは呼ばれない return 0; }
実行結果
hoge1->number_ : 30 hoge2->number_ : 30 hoge1->number_ : 30
Hogeのデストラクタが呼ばれていないのが分かります。
参照はずし
参照はずしも普通のポインタ同様に行えます
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { { std::shared_ptr<Hoge> hoge1(new Hoge); hoge1->number_ = 15; Hoge hoge2 = *hoge1; std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; std::cout << "hoge2.number_ : " << hoge2.number_ << std::endl; std::cout << std::endl << "hoge2.number_ = 3" << std::endl << std::endl; hoge2.number_ = 3; std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; std::cout << "hoge2.number_ : " << hoge2.number_ << std::endl; } return 0; }
実行結果
hoge1->number_ : 15 hoge2.number_ : 15 hoge2.number_ = 3 hoge1->number_ : 15 hoge2.number_ : 3 Hogeのデストラクタだよ Hogeのデストラクタだよ
use_count()で参照しているshared_ptrの数を調べる
hoge1->とするとHogeへのポインタとして使えますが、
hoge1.とするとshared_ptrとしての機能を呼び出すことが出来ます。
hoge1.use_count()でオブジェクトを指し示しているshared_ptrの数を知ることができます。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { { std::shared_ptr<Hoge> hoge1(new Hoge); hoge1->number_ = 15; std::cout << "hoge1.use_count() : " << hoge1.use_count() << std::endl; std::shared_ptr<Hoge> hoge2 = hoge1; std::cout << "hoge1.use_count() : " << hoge1.use_count() << std::endl; std::cout << "hoge2.use_count() : " << hoge2.use_count() << std::endl; } return 0; }
実行結果
hoge1.use_count() : 1 hoge1.use_count() : 2 hoge2.use_count() : 2 Hogeのデストラクタだよ
注意しなければならないのは、"あるアドレスを指すshared_ptrの数"に基づいているわけじゃなくて、"あるshared_ptrが何個コピーされたか"に基づいてる(?)ってことです。
あんまりうまく説明できている気がしないですが、何がいいたいかというと、次のようなコードをかいちゃだめだよってことです。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { Hoge* hoge = new Hoge; hoge->number_ = 11; std::shared_ptr<Hoge> hoge1(hoge); { std::shared_ptr<Hoge> hoge2(hoge); // 同じアドレスを指すshared_ptrが二つあるけど、 std::cout << "hoge1.use_count() : " << hoge1.use_count() << std::endl; std::cout << "hoge2.use_count() : " << hoge2.use_count() << std::endl; } // ここでhoge2がスコープをぬけてデストラクタを呼んでしまう! std::cout << hoge1->number_ << std::endl; // 削除済みの領域にアクセスしようとしてエラーになる return 0; }
hoge1.use_count() : 1 hoge2.use_count() : 1 Hogeのデストラクタだよ -572662307 Hogeのデストラクタだよ
意外だったのですが、削除済みの領域のデストラクタを呼ぼうとするとちゃんと呼ばれるんですね。オブジェクトがどうこうってんじゃなくて型の情報から呼ばれるんでしょうか。
なんしか、同じアドレスを指していても、代入とかの方法で明示的に同じであるってことをshared_ptrに伝えてやらないといけないってことです。
あと、今みたいなコードは書くべきじゃないですね。
.get()で生のアドレスを取得
hoge1.get()で生のアドレスを取得できるそうです。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; }; int _tmain(int argc, _TCHAR* argv[]) { Hoge* hoge; { std::shared_ptr<Hoge> hoge1(new Hoge); hoge1->number_ = 5; hoge = hoge1.get(); hoge->number_ = 10; // 取得した生アドレスを介して操作 std::cout << "hoge1->number_ : " << hoge1->number_ << std::endl; // 変更が反映されてる std::cout << "hoge->number_ : " << hoge->number_ << std::endl; } std::cout << "hoge->number_ : " << hoge->number_ << std::endl; // 削除済みの領域にアクセス return 0; }
実行結果
hoge1->number_ : 10 hoge->number_ : 10 Hogeのデストラクタだよ hoge->number_ : -572662307
当たり前ですが、.get()で生のアドレスを取得したからってshared_ptrはそんなことにお構いなくスコープを抜けた時点でデストラクタを呼んでくれます。
うまくデストラクタを呼んでくれないとき(循環参照)
shared_ptrがうまくデストラクタを呼んでくれないことがあります。
循環参照が存在するとそれらのデストラクタは呼ばれないそうです。
#include "stdafx.h" #include <memory> #include <iostream> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; std::shared_ptr<Hoge> hoge_; }; int _tmain(int argc, _TCHAR* argv[]) { { std::shared_ptr<Hoge> hoge1(new Hoge); std::shared_ptr<Hoge> hoge2(new Hoge); // 循環参照 hoge1->hoge_ = hoge2; hoge2->hoge_ = hoge1; }// スコープを抜けてもdeleteが呼ばれない return 0; }
実行結果
(デストラクタが呼ばれていない)
リンクドリストの先頭と末尾をつないだりするとこれが起こりそうですね。
vectorにつっこんでみる
std::vectorに入れたりすると、(他から参照されていないかぎり)eraseするだけでデストラクタも呼んでくれます。
#include "stdafx.h" #include <memory> #include <iostream> #include <vector> #include <algorithm> #include <functional> class Hoge { public: Hoge(int i) : number_(i){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; std::shared_ptr<Hoge> hoge_; }; int _tmain(int argc, _TCHAR* argv[]) { std::vector<std::shared_ptr<Hoge> > hoges; hoges.push_back(std::shared_ptr<Hoge>(new Hoge(0))); hoges.push_back(std::shared_ptr<Hoge>(new Hoge(1))); hoges.push_back(std::shared_ptr<Hoge>(new Hoge(2))); hoges.push_back(std::shared_ptr<Hoge>(new Hoge(3))); hoges.push_back(std::shared_ptr<Hoge>(new Hoge(4))); std::tr1::function<void(std::shared_ptr<Hoge>)> PrintNumbers = [](std::shared_ptr<Hoge> h)->void{std::cout << h->number_ << std::endl;}; std::for_each(hoges.begin(), hoges.end(), PrintNumbers); auto p = hoges.begin(); hoges.erase(p + 2); // 他に参照されていないのでeraseした時点でデストラクタが呼ばれる std::shared_ptr<Hoge> hoge1(hoges.at(1)); hoges.erase(p + 1); // hoge1も参照しているのでeraseしてもデストラクタは呼ばれない std::for_each(hoges.begin(), hoges.end(), PrintNumbers); return 0; }
実行結果
0 1 2 3 4 Hogeのデストラクタだよ 0 3 4 Hogeのデストラクタだよ Hogeのデストラクタだよ Hogeのデストラクタだよ Hogeのデストラクタだよ
.reset()でリセット
.reset()を呼ぶと、呼んだ変数がオブジェクトを指し示しているっていう情報がリセットされ、参照カウントが一つ減ります。
.reset()を呼んだときに参照カウントが0になったらデストラクタが呼ばれますよ。
#include "stdafx.h" #include <memory> #include <iostream> #include <vector> #include <algorithm> #include <functional> class Hoge { public: Hoge(){} ~Hoge(){ std::cout << "Hogeのデストラクタだよ" << std::endl; } int number_; std::shared_ptr<Hoge> hoge_; }; int _tmain(int argc, _TCHAR* argv[]) { { std::shared_ptr<Hoge> hoge1(new Hoge); std::shared_ptr<Hoge> hoge2 = hoge1; hoge1->number_ = 3; hoge1.reset(); std::cout << "hoge2->number_ : " << hoge2->number_ << std::endl; hoge2.reset(); std::cout << "まだスコープぬけてないよ" << std::endl; } return 0; }
実行結果
hoge2->number_ : 3 Hogeのデストラクタだよ まだスコープぬけてないよ
ちょっと分かりにくいですがスコープを抜ける前に.reset()で参照カウントが0になりデストラクタが呼ばれているのが確認できますね。
まとめ
こんな感じでしょうか。
勉強になりました。