89
95

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++ スマートポインタのパターン

Posted at

はじめに

数年前、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 で導入されたスマートポインタの使い分けについて書いてみた。昔はもっといろいろ迷っていた気がするので、たぶんもっと挙げるべき例があるのだと思う。こんなときは ? みたいなコメントぜひお待ちしています。
89
95
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
89
95

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?