- 追加された行はこの色です。
- 削除された行はこの色です。
第5回はスマートポインタになります。
C++ではメモリ確保を行う場合、自動で解放しないので解放処理を書く必要があります。
1: #include <iostream>
2:
3: using namespace std;
4:
5: class Hoge
6: {
7: public:
8: Hoge()
9: {
10: cout << "Constructor" << endl;
11: }
12: ~Hoge()
13: {
14: cout << "Destructor" << endl;
15: }
16:
17: int num;
18: };
19:
20: int main()
21: {
22: //メモリ確保
23: Hoge* p1 = new Hoge();
24: Hoge* p2 = new Hoge[5];
25:
26: //初期化
27: p1->num = 10;
28: for (int i = 0; i < 5; i++)
29: {
30: (p2+i)->num = i;
31: }
32:
33: //表示
34: cout << p1->num << endl;
35: for (int i = 0; i < 5; i++)
36: {
37: cout << (p2+i)->num << endl;
38: }
39:
40: //メモリ解放
41: delete p1;
42: delete [] p2;
43
44: return 0;
45: }
40行目から先のdeleteを書かないと、確保したメモリが終了しても解放されずに残ってしまいます。
上記のプログラム程度ならdeleteを書き忘れることはないと思いますが、大規模なプログラムになると、
newとdeleteが各所に散らばるため確認が難しくなります。
C++11では自動で解放処理をしてくれるスマートポインタが登場しました。
新しく<memory>をインクルードすることにより
・unique_ptr
・shared_ptr
・weak_ptr
の3つのスマートポインタが使えるようになります。
最初のプログラムのメモリ確保部分を書き直すと
1: int main()
2: {
3: //メモリ確保
4: unique_ptr<Hoge> p1(new Hoge);
5: unique_ptr<Hoge[]> p2(new Hoge[5]);
6:
7: //初期化
8: p1->num = 10;
9: for (int i = 0; i < 5; i++)
10: {
11: p2[i].num = i;
12: }
13:
14: //表示
15: cout << p1->num << endl;
16: for (int i = 0; i < 5; i++)
17: {
18: cout << p2[i].num << endl;
19: }
20:
22: return 0;
23: }
deleteを書いていませんが、実行するとデストラクタが呼ばれることが分かります。
unique_ptrはスコープを抜けた時点でポインタを自動でdeleteします。
次にshaerd_ptrの例を見ます。
1: int main()
2: {
3: //メモリ確保
4: shared_ptr<Hoge> p1 = make_shared<Hoge>();
5:
6: cout << p1.use_count() << endl;//1
7:
8: {
9: //p2に代入
10: shared_ptr<Hoge> p2 = p1;
11:
12: cout << p1.use_count() << endl;//2
13:
14: p2->num = 10;
15: }//p2はここで解放される(Hogeは破棄されない)
16:
17: cout << p1.use_count() << endl;//1
18:
19: cout << p1->num << endl;//p2で代入した10が表示される
20:
21: return 0;
22: }
shaerd_ptrは参照カウント方式で、オブジェクトを参照するshaer_ptrが0になったときにdeleteします。
先ほどのunique_ptrは複数のunique_ptrで1つのオブジェクトを参照することが出来ませんが、
shaerd_ptrは複数のshaer_ptrで1つのオブジェクトを参照することが出来ます。
次にweak_ptrを使う例について説明します。
先ほどのshaerd_ptrは循環参照になった場合に解放されない問題があります。
※実行しないでください
1: #include <iostream>
2: #include <memory>
3:
4: using namespace std;
5:
6: class Hoge
7: {
8: public:
9: Hoge()
10: {
11: cout << "Constructor" << endl;
12: }
13: ~Hoge()
14: {
15: cout << "Destructor" << endl;
16: }
17:
18: shared_ptr<Hoge> ptr;
19: };
20:
21: int main()
22: {
23: //メモリ確保
24: shared_ptr<Hoge> p1(new Hoge);
25: shared_ptr<Hoge> p2(new Hoge);
26:
27: p1->ptr = p2;
28: p2->ptr = p1;
29:
30 return 0;
31: }
このプログラムだと循環参照が発生しているため、メモリの解放が行われません。
本当にメモリリークしているかの確認はメモリリーク検出を参考(Visual C++環境下)
この解決方法として弱い参照であるweak_ptrがあります。
weak_ptrはshaerd_ptrが持っているオブジェクトを参照するポインタです。shaerd_ptrと違う点として、カウンタに関係なくスコープを抜けた時点でdeleteが呼ばれます。
1: class Hoge
2: {
3: public:
4: Hoge()
5: {
6: cout << "Constructor" << endl;
7: }
8: ~Hoge()
9: {
10: cout << "Destructor" << endl;
11: }
12:
13: weak_ptr<Hoge> ptr;//weak_ptrに変える
14: };
これによってスコープを抜けた時点でカウンタが残っていてもdeleteが呼ばれるため先ほどの循環参照の問題がなくなりました。
また、weak_ptrは参照先が生存しているかを確認する機能があります。
1: int main()
2: {
3: //メモリ確保
4: shared_ptr<Hoge> shar_p = make_shared<Hoge>();
5: weak_ptr<Hoge> weak_p;
6:
7: weak_p = shar_p;
8:
9: //weak_ptrからは直接操作出来ないので、lock()を使うことにより取得出来る
10: if (auto p = weak_p.lock())
11: {
12: p->num = 10;
13: }
14:
15: cout << shar_p->num << endl;//10が表示される
16:
17: shar_p.reset();//解放
18:
19: //解放されているかの確認はexpired()で出来る
20: if (weak_p.expired())
21: {
22: cout << "expired" << endl;
23: }
24:
25: return 0;
26: }
スマートポインタを利用することによって、メモリ解放に関する問題を解決することが出来ます。
unique_ptrはスコープを抜けた時点でdeleteするという単純な動作なのですぐに使用出来るようになると思います。
shaerd_ptr、weak_ptrは少し癖がありますが、どちらも強力な機能なので覚えるようにしましょう。
以上でスマートポインタの説明を終了します。
前→C++11講座4回
次→C++11講座6回
#vote(そのためのポインタ[0],C++こわれる[0],エイシャア[0])
#vote(そのためのポインタ[1],C++こわれる[1],エイシャア[1])