前提
スマートポインタいろいろ
- std::auto_ptr : C++98からあった。いろいろ欠点があり、C++11では非推奨。
- std::unique_ptr : std::auto_ptrの改良版のようなやつ。C++11ではこちらを使うべし。
- std::shared_ptr : 一つのオブジェクトを複数のスマートポインタから参照できる。
- std::weak_ptr : 弱参照する。std::shared_ptrだと循環参照に陥る場合とかに用いる。
- boost::scoped_ptr : std::unique_ptrの前身? しらんけど。
- boost::shared_ptr : std::shared_ptrの前身? しらんけど。
- boost::weak_ptr : std::weak_ptrの前身? しらんけど。
C++11使えること前提なら、std::unique_ptrとstd::shared_ptr、おまけでstd::weak_ptrをおさえておけばよい。オブジェクトの所有権を独占する(std::unique_ptr)か共有する(std::shared_ptr)に明示的な区別があるのがC++のスマートポインタの特徴。JavaやC#の参照にはそのような概念はない。
std::unique_ptr
- いちばん基本的なスマートポインタ
- オブジェクトの所有権を独占する。
- つまり、あるスマートポインタのスコープから出るときにオブジェクトは必ず解放される。
- そのため参照のコピーは出来ないが、所有権を譲渡することはできる。
サンプルコード
#include <iostream> #include <memory> // スマートポインタを使うために必要 // テキトーなクラス class Hoge { public: Hoge() { std::cout << "constructor" << std::endl; } ~Hoge() { std::cout << "destructor" << std::endl; } void message() { std::cout << "message" << std::endl; } }; int main(void) { // 生ポインタの場合 std::cout << "Raw Pointer:" << std::endl << std::endl; for (int i = 0; i < 3; ++i) { Hoge* hoge = new Hoge(); hoge->message(); } // スマートポインタの場合 std::cout << std::endl << "Smart Pointer:" << std::endl << std::endl; for (int i = 0; i < 3; ++i) { std::unique_ptr<Hoge> hoge(new Hoge()); // 定義の記述はちょっとウザいが hoge->message(); // 生ポインタと同様の記述で使える } return 0; }
実行結果
生ポインタだとデストラクタは呼ばれないが、スマートポインタだと呼ばれる。
Raw Pointer: constructor message constructor message constructor message Smart Pointer: constructor message destructor constructor message destructor constructor message destructor
std::shared_ptr
- 一つのオブジェクトを複数のポインタから参照したいときに使うスマートポインタ
- オブジェクトの所有権を共有する。
- そのために、オブジェクトを参照しているポインタの数が参照カウンタで監視されている。
- 参照カウンタがゼロになったときにオブジェクトは解放される。
サンプルコード
#include <iostream> #include <string> #include <memory> // テキトーなクラス class Hoge { public: Hoge(std::string name) { m_name = name; std::cout << "constructor of " << m_name << std::endl; } ~Hoge() { std::cout << "destructor of " << m_name << std::endl; } void message() { std::cout << "I am " << m_name << std::endl; } private: std::string m_name; }; int main() { // std::unique_ptrの場合 std::cout << "std::unique_ptr:" << std::endl; { std::unique_ptr<Hoge> u1(new Hoge("u1")); u1->message(); // std::unique_ptr<Hoge> u2 = u1; ←コンパイルエラーになる (参照のコピー不可) } // std::shared_ptrの場合 std::cout << std::endl << "std::shared_ptr:" << std::endl; { std::shared_ptr<Hoge> s1(new Hoge("s1")); s1->message(); { std::shared_ptr<Hoge> s2 = s1; // 参照のコピー可 s2->message(); std::cout << "s2's scope ends" << std::endl; } std::cout << "s1's scope ends" << std::endl; } return 0; }
実行結果
std::unique_ptr: constructor of u1 I am u1 destructor of u1 std::shared_ptr: constructor of s1 I am s1 I am s1 s2's scope ends s1's scope ends destructor of s1
std::weak_ptr
- 参照するだけで所有権には関与しない。
- つまり参照カウンタの増減に影響しない。
- したがって、このポインタが生存中でも、オブジェクトは既に解放されているかもしれない。
- 生ポインタとちがって、オブジェクトが解放されたかをチェックすることができる。
- std::shared_ptrだと循環参照に陥る場合とかに用いる。
サンプルコード
include <iostream> #include <string> #include <memory> // テキトーなクラス class Hoge { public: Hoge(std::string name) { m_name = name; std::cout << "constructor of " << m_name << std::endl; } ~Hoge() { std::cout << "destructor of " << m_name << std::endl; } std::shared_ptr<Hoge> s_ptr; std::weak_ptr<Hoge> w_ptr; private: std::string m_name; }; int main() { // 循環参照の例 std::cout << "Circular Reference" << std::endl; { std::shared_ptr<Hoge> s1(new Hoge("s1")); std::shared_ptr<Hoge> s2(new Hoge("s2")); s1->s_ptr = s2; s2->s_ptr = s1; } // 弱参照の例 std::cout << std::endl << "Weak Reference" << std::endl; { std::shared_ptr<Hoge> s1(new Hoge("s1")); std::shared_ptr<Hoge> s2(new Hoge("s2")); s1->s_ptr = s2; s2->w_ptr = std::weak_ptr<Hoge>(s1); // 片方を弱参照にする } return 0; }
実行結果
循環参照だとオブジェクトが解放されずメモリリークするが、片方が弱参照だと解放される。
Circular Reference constructor of s1 constructor of s2 Weak Reference constructor of s1 constructor of s2 destructor of s1 destructor of s2