C++11 でスマートポインタを使用する - C++ プログラミング

PROGRAM


C++11 でスマートポインタを使用する

C++11 には 3 つのスマートポインタが規定されました。

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

スマートポインタというのは、ポインタに格納したメモリを自動で管理してくれる機能を持ったポインタです。

それぞれの特徴を整理すると次のようになります。

std::unique_ptr std::shared_ptr std::weak_ptr
管理方法 唯一のポインタ ポインタを共有(所有権あり) ポインタを共有(所有権なし)
ポインタの取得 get() get() -
ポインタの管理放棄 release() - release()
ポインタ値の代入 reset(p) reset(p) -
スマートポインタの代入
(代入演算子や構築時)
unique_ptr(右辺値参照) unique_ptr(右辺値参照)
shared_ptr
weak_ptr(コンストラクタ)
shared_ptr
weak_ptr
参照カウントの取得 - use_count() use_count()

ここでは、これらの特色の違いと使い方の詳細を見て行きます。

なお、これらのスマートポインタを使用する場合は <memory> ヘッダーをインクルードしておく必要があります。

 

std::unique_ptr

std::unique_ptr は、従来のただのポインタに、スコープを抜ける時にその内容を消去する機能を付けたスマートポインタです。

従来のポインタと互換性のある操作を提供しながら、スコープを抜けるときに割り当てられているメモリを解放してくれるので、解放し忘れを防ぐことができます。

 

std::unique_ptr の利用準備

std::unique_ptr を宣言するとき、このポインタが扱うデータ型(ポインタが指す先のデータ型)を指定します。

そしてコンストラクタで、new 演算子を使って確保した値を指定すると、その値がこのスマートポインタによって管理されるようになります。

// int 型の std::unique_ptr スマートポインタを構築します。初期値として 120 を設定しています。

std::unique_ptr<int> pInt(new int(120));

もちろん、いったん new して確保した値を、std::unique_ptr のコンストラクタに渡しても大丈夫です。

// もちろん普通のポインタにメモリを生成してから、それをスマートポインタの管理に移すこともできます。

int* source = new int(120);

std::unique_ptr<int> pInt(source);

ただし、どちらの場合も new 演算子を使ってメモリを確保していますが、このメモリは std::unique_ptr がスコープを抜ける時に解放してくれるので、プログラマ側が delete で解放してはいけません。

 

std::unique_ptr への再代入

ポインターをスマートポインタの定義とは遅らせて設定したい場合や、新しいポインタを代入したい場合は reset 関数を使います。

// std::unique_ptr 型への

std::unique_ptr<int> pInt;

 

pInt.reset(new int(120));

このようにすることで、std::unique_ptr を定義した後でも、値を設定することができます。

このとき reset 関数を呼び出した std::unique_ptr が既にポインタを管理している場合には、それを安全に破棄した上で、新たに指定されたポインターを管理するようになります。

 

std::unique_ptr が管理しているポインタを取得する

std::unique_ptr が管理しているポインタを別の関数に渡したい場合など、ポインタをそのまま取得したいことがあります。

そのようなときには get 関数を使うことで、管理しているポインタの値をそのまま取得することができます。

// get 関数で取得したポインタはスマートポインタの管理下に置かれるため、勝手に解放してはいけません。

int* p = pInt.get();

ただし get 関数で取得できたポインタが指すメモリは std::unique_ptr の管理下にあるため、これを勝手に解放してはいけません。

 

ポインタを取り出して以降、そのポインタの管理を std::unique_ptr から離したい場合は release 関数を使ってポインタを取得します。

// release 関数で取得したポインタはスマートポインタの管理下から離れるため、使い終わったら自分で解放しなくてはいけません。

int* p = pInt.release();

このように release 関数を使って取得したポインタは std::unique_ptr の管理下から離れるため、std::unique_ptr のスコープを抜けてもメモリに存在し続けます。

そのため、ポインタの値を使い終わったら、自分で解放する必要があります。

 

std::unique_ptr がポインタを管理しているか調べる

std::unique_ptr が内部にポインタを持っているかどうかは、std::unique_ptr を bool 型にキャストすることで判断できます。

if (pInt)

{

// スマートポインタが値を管理している場合に true の処理が行われます。

 

}

std::unique_ptr を bool 型にキャストすると、内部にポインタを持っている場合に true を返します。そのため、条件文にそのままスマートポインタを指定することで、それが値を持っていないことを判定できます。

 

std::unique_ptr は複製できない

std::unique_ptr では、内部で持っているポインタのメモリを独占的に管理しているので、同じポインタを複数の std::unique_ptr が管理することはできません。

そのため、ある std::unique_ptr をそのまま別の std::unique_ptr へ代入するような代入文はありません。

 

std::shared_ptr

std::unique_ptr は、ひとつのポインタを複数の場所から安全に使用するためのスマートポインタです。

従来のポインタと互換性のある操作を提供しながら、そのポインタを誰が使っているのかを管理して、だれも使わなくなったタイミングで自動てメモリを解放してくれるので、プログラマ側のメモリ管理の負担を少なくできます。

 

ポインタを誰が使っているかという情報は、参照カウンタで保持します。

誰かがスマートポインタを所持するときには参照カウンタが 1 つ増加され、誰かが使い終わったら 1 つ減少します。そして参照カウンタが 0 になったとき、そのスマートポインタが管理しているメモリが解放される仕組みになっています。

 

std::shared_ptr の利用準備

std::shared_ptr を宣言するときは、std::unique_ptr と同じように、このポインタが扱うデータ型(ポインタが指す先のデータ型)を指定します。

そして std::shared_ptr の場合は、保持する値は、新しい値か、既に存在する std::shared_ptr かのどちらかになります。

 

新しい値を設定する場合には、std::unique_ptr と同じように、コンストラクタを使って new  演算子を使って確保した値を指定すると、その値がこのスマートポインタによって管理されるようになります。

// int 型の std::shared_ptr スマートポインタを新規に構築します。初期値として 150 を設定しています。

std::shared_ptr<int> pInt(new int(150));

これで、参照カウントが 1 の新しいスマートポインタを生成することができました。

 

また、既存の std::unique_ptr 型のスマートポインタの値をそのまま譲り受けることもできます。

// int 型の std::unique_ptr スマートポインタがあったとします。

std::unique_ptr<int> pUnique(new int(150));

 

// それを std::shared_ptr スマートポインタへ値を譲り渡すことができます。

std::shared_ptr<int> pShared = std::move(pUnique);

このようにすることで、1 箇所でしか使えなかった std::unique_ptr を、複数個所で安全に使える std::shared_ptr に変換することができます。

このとき、代入元の std::unique_ptr は、保持していたポインタを完全に譲り渡し、何も管理していないスマートポインタになります。

このあたりの std::move についての挙動は 右辺値参照とムーブコンストラクタの使い方 で詳しく記載してあります。

 

既存の std::shared_ptr を共有する

std::shared_ptr では、既に生成されている std::shared_ptr を使ってスマートポインタを生成することができます。

// ある std::shared_ptr スマートポインタが定義されています。

std::shared_ptr<int> p1(new int(150));

 

// それを別の std::shared_ptr スマートポインタへ代入することで、同じポインタを共有できます。

std::shared_ptr<int> p2 = p1;

このように、代入文を使って std::shared_ptr を代入すると、代入元と同じポインタを共有するスマートポインタを生成することができます。

 

このとき、共有されたポインタは、何個のスマートポインタによって共有されているかを参照カウンタで管理しています。

このポインタを指すスマートポインタの参照カウントが 1 つ増加されます。また、代入先が既にポインタを持っていた場合には、その参照カウントを 1 つ減少させてから新しい値を設定してくれます。

これにより、誰かがポインタを共有している場合に限って、そのポインタが指すメモリを確保しておくことができています。

 

std::shared_ptr への再代入

std::shared_ptr にこれまでとは別の値を設定したい場合にも、std::unique_ptr と同じ reset 関数を使用できます。

// std::unique_ptr 型への

std::shared_ptr<int> pInt;

 

pInt.reset(new int(120));

このようにすることで、スマートポインタに格納されている値を、新しく指定した値に置き換えることができます。

このとき、この reset 関数を呼び出した std::shared_ptr については値が変更されますが、同じポインタを共有している他の std::shared_ptr があった場合でもそれには影響がないため、以前のまま使用できます。

また、reset 関数を呼び出したときに、これまで使っていたポインタの参照カウントを 1 引いてくれるので、プログラマが参照カウントを気にする必要はありません。

 

std::shared_ptr が管理しているポインタを取得する

std::shared_ptr が管理しているポインタをそのまま取得したいときには、std::unique_ptr と同じく get 関数を使って取得します。

// get 関数で取得したポインタは std::shared_ptr で共有されているため、勝手に解放してはいけません。

int* p = pInt.get();

ただし get 関数で取得できたポインタが指すメモリは std::shared_ptr の管理下にあります。

このポインタを複数箇所で共有している可能性もあるため、絶対にこれを勝手に解放してはいけません。

 

なお、std::shared_ptr には、std::unique_ptr のような、ポインタを管理下から除外する release のような関数はありません。

 

std::shared_ptr がポインタを管理しているか調べる

std::shared_ptr が内部にポインタを持っているかどうかは、std::unique_ptr と同様、スマートポインタを bool 型にキャストすることで判断できます。

if (pInt)

{

// スマートポインタが値を管理している場合に true の処理が行われます。

 

}

std::shared_ptr を bool 型にキャストすると、内部にポインタを持っている場合に true を返します。そのため、条件文にそのままスマートポインタを指定することで、それが値を持っていないことを判定できます。

 

std::shared_ptr が何か所で共有されているかを調べる

std::shared_ptr は、ポインタが共有されている数を "参照カウント" を使って管理しています。

この参照カウントを use_count 関数を使って取得することで、何か所から共有されているかを知ることができます。

long referenceCount = pInt.use_count();

また、スマートポインタが持っているポインタが、他のどこでも共有されていないことを unique 関数を使って知ることもできます。

if (pInt.unique())

{

// このスマートポインタが管理するポインタが、他の場所で共有されていない場合に true の処理が行われます。

 

}

 

std::weak_ptr

std::weak_ptr は、std::shared_ptr と組み合わせて使用します。

std::shared_ptr の方は共有という形で所有権を所持しますが、std::weak_ptr は所有権を保持しない形で std::shared_ptr が管理しているポインタのアクセスを行うことができます。

ポインタが指す値を使って何かをするときには、std::weak_ptr からいったん std::shared_ptr を取り出して操作します。

 

この std::weak_ptr の特徴としては、所有権を所持しないため、たとえ std::weak_ptr でポインタを利用していたとしても、そのポインタを共有している std::shared_ptr がひとつも居なくなった時点で、そのポインタは解放されることになります。

この std::weak_ptr のメリットはここにあって、ポインタの所有権を持たないことで、ポインタの解放タイミングを完全に外部に任せることができます。

ポインタが解放されたかを知る関数も用意されているので、解放されていた場合の処理もプログラムできます。

 

std::weak_ptr の利用準備

std::weak_ptr の宣言は、std::shared_ptr と同じように、このポインタが扱うデータ型(ポインタが指す先のデータ型)を指定します。

ただし、このとき、std::weak_ptr に新しい値を設定することはできません。

// int 型の std::shared_ptr スマートポインタを構築します。新しい値は設定できません。

std::unique_ptr<int> pWeak;

 

std::weak_ptr への代入

std::weak_ptr には、既存の std::shared_ptr または std::weak_ptr だけを代入できます。

// int 型の std::shared_ptr スマートポインタがあったとします。

std::shared_ptr<int> pShared(new int(150));

 

// それを std::weak_ptr スマートポインタに代入することができます。

std::weak_ptr<int> pWeak = pShared;

このとき、std::weak_ptr はポインタの所有権を取得しないため、std::shared_ptr が管理しているポインタの参照カウントが増加することはありません。

std::weak_ptr に std::weak_ptr を代入したときも同様です。

 

std::weak_ptr が管理しているポインタを操作する

std::weak_ptr が管理しているポインタの値を操作したい場合は lock 関数を使って、ポインタをいったん std::shared_ptr として取り出してそれを使います。

std::shared_ptr<int> pShared = pWeak.lock();

このとき、std::shared_ptr によって、std::weak_ptr が共有していたポインタの所有権が取得されるため、ポインタを使っている間は勝手にどこかで解放されることはなくなります。

std::shared_ptr に取り出した後のさまざまな操作は、std::shared_ptr の使い方で記した通りです。

 

使い終わった後は、std::shared_ptr がスコープを抜ければ自動で所有権を手放してくれるので、特に何もする必要はありません。

 

std::weak_ptr のポインタが解放されていないか調べる

std::weak_ptr はポインタの所有権を取らないため、いつの間にかにポインタが解放されている場合があります。

それを知るには expired 関数を使います。

if (pWeak.expired())

{

// Weak スマートポインタが解放されている場合に true の処理が行われます。

 

}

これによって、std::weak_ptr が既に解放されていた場合でも、適切なプログラムを組むことができます。

 

スマートポインタ共通の特徴

スマートポインタへのアクセス

これらのスマートポインタは、できる限り素のままのポインタに似た動作を提供してくれます。

スマートポインタ自体は、ポインタを内部で管理してくれるクラスですが、メンバ選択演算子 (->) や間接演算子 (*)、配列参照演算子 ([]) を使って、スマートポインタが内部で管理しているポインタに透過的にアクセスすることができます。

 

スマートポインタに C 配列を格納する

std::unique_ptr<T> の場合

配列をスマートポインタに格納したい場合、std::unique_ptr であれば次のようにデータ型のところで [] を使って定義できます。

// int 型の配列を std::unique_ptr で扱う場合の定義です。

std::unique_ptr<int[]> pIntArray(new int[10]);

たとえばこのようにすることで、要素の数が 10 個の int 型配列をスマートポインタで管理できるようになりました。

スマートポインタが解放されるときには、管理されているメモリが delete [] を使って自動的に解放されます。

 

std::shared_ptr<T> の場合

ただ std::shared_ptr の場合には、先ほどのような方法では定義することができません。

こちらの場合は、たとえば char 型の配列を持たせたい場合は、定義自体は std::shared_ptr<char> で定義した上で、構築時にポインタと合わせて delete 時の振る舞いを指定します。

// char 型の配列を std::shared_ptr で扱う場合の定義です。

std::shared_ptr<char> pCharArray(new char[10], std::default_delete<char[]>());

このように、第二引数で "std::default_delete<char[]>()" を指定することで、スマートポインタで管理されている配列を delete [] によって char 配列を解放されるようにします。


[ もどる ]