はじめに
数年前、C++11 の std::unique_ptr
, std::shared_ptr
を使い始めたころ、機能は理解できるんだけど実際のコードでどう使うのがよいのか ? がしっくりこずに試行錯誤していた覚えがある。
今となってはなかば無意識に使っているが、あのころの自分のように迷っている人向けに、自分の使い方を整理してみる。
という主旨なので、右辺値参照とかカスタムデリータとかのちょっとややこしい話は省きます。
整理してみる
登場人物
-
T*
/const T*
: 生ポインタ。 std::unique_ptr<T>
std::shared_ptr<T>
-
std::weak_ptr<T>
(本稿ではあまり触れない)
関数の引数
- 関数を通して 所有権を渡したい ときは
std::unique_ptr
,std::shared_ptr
を使う。-
std::unique_ptr
が完全に所有権を譲渡、std::shared_ptr
は共有する可能性がある。
-
- それ以外は生ポインタ(または参照)
関数の戻り値
- ファクトリ関数 (オブジェクトを生成する関数) の戻り値は原則
std::unique_ptr
。最初からスマートポインタ管理にしておけば、リークするリスクは激減する。 -
std::shared_ptr
で扱いたい場合は、受け取ったstd::unique_ptr
をstd::shared_ptr
に入れれば良い (std::shared_ptr
のコンストラクタにstd::unique_ptr&&
を取るものがある)
std::unique_ptr<Foo> CreateFoo(); // こんな factory method で作ったオブジェクトを…
std::shared_ptr<foo> sp = CreateFoo(); // std::shared_ptr に入れる
- オブジェクト内のメンバへのポインタを返す際などは生ポインタ、あるいは参照でよい。変更不要なら
const
にすること。オブジェクトの生存期間には注意が必要。
class Foo {
public:
const Bar& GetFoo() const { return bar_; }
private:
Bar bar_;
};
クラスのメンバ変数
- 所有権を持っているなら
std::unique_ptr
かstd::shared_ptr
- 所有権を持っていないなら、基本は生ポインタか参照でよい。この場合、参照しているオブジェクトがなくなってしまわないよう、生存期間の設計には注意が必要。
- 生存期間をコントロールしにくい場合、
std::weak_ptr
を使うこともある (がそれほど多くはない)
std::shared_ptr
/ std::weak_ptr
は必要なときだけ使う
-
std::shared_ptr
は本当に所有権を本当に共有したいときだけでいい。むやみに使わない。それよりもオブジェクトの生存期間をコントロールして設計しよう。std::shared_ptr
を使うことで、オブジェクトの生存期間はむしろコントロールしにくくなってしまう。 - 同じように、
std::weak_ptr
(弱参照) もむやみに使うものでもない。複数の場所で使われるオブジェクトの生存期間をコントロールするのが難しいときとか、循環参照の解決には便利。「所有権を持たないオブジェクトへの参照は全てstd::weak_ptr
で持つ」みたいなことはしない
スマートポインタのコスト
- 通常 (カスタムデリーなし)は
std::unique_ptr
のオーバーヘッドはない。メモリ使用量、コードサイズ、実行速度全てにおいて、生ポインタを使って手でnew
delete
する場合に負けることはない。 -
std::shared_ptr
/std::weak_ptr
はそこそこオーバーヘッドがある。- コントロールブロックと呼ばれる別のメモリ領域がヒープから取られる。 よって、格納するオブジェクトと合わせて 2回のメモリ確保が行われることになる。(
std::make_shared()
を使えばこれは1回にできる) - コピー時にはリファレンスカウントの操作が行われる。これはオブジェクトの生成・破棄に比べれば遥かに軽い操作であるが、スレッドセーフにするために排他されていたりもするので無料ではない (でも高速な実装になっていることが多い)
- コードサイズも大きくなる (ほとんどの環境では無視できるとは思う)
- コントロールブロックと呼ばれる別のメモリ領域がヒープから取られる。 よって、格納するオブジェクトと合わせて 2回のメモリ確保が行われることになる。(
まとめ
- C++11 で導入されたスマートポインタの使い分けについて書いてみた。昔はもっといろいろ迷っていた気がするので、たぶんもっと挙げるべき例があるのだと思う。こんなときは ? みたいなコメントぜひお待ちしています。