はじめに

数年前、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_ptrstd::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_ptrstd::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回にできる)
    • コピー時にはリファレンスカウントの操作が行われる。これはオブジェクトの生成・破棄に比べれば遥かに軽い操作であるが、スレッドセーフにするために排他されていたりもするので無料ではない (でも高速な実装になっていることが多い)
    • コードサイズも大きくなる (ほとんどの環境では無視できるとは思う)

まとめ

  • C++11 で導入されたスマートポインタの使い分けについて書いてみた。昔はもっといろいろ迷っていた気がするので、たぶんもっと挙げるべき例があるのだと思う。こんなときは ? みたいなコメントぜひお待ちしています。
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.