はじめに
数年前、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のオーバーヘッドはない。メモリ使用量、コードサイズ、実行速度全てにおいて、生ポインタを使って手でnewdeleteする場合に負けることはない。 -
std::shared_ptr/std::weak_ptrはそこそこオーバーヘッドがある。- コントロールブロックと呼ばれる別のメモリ領域がヒープから取られる。 よって、格納するオブジェクトと合わせて 2回のメモリ確保が行われることになる。(
std::make_shared()を使えばこれは1回にできる) - コピー時にはリファレンスカウントの操作が行われる。これはオブジェクトの生成・破棄に比べれば遥かに軽い操作であるが、スレッドセーフにするために排他されていたりもするので無料ではない (でも高速な実装になっていることが多い)
- コードサイズも大きくなる (ほとんどの環境では無視できるとは思う)
- コントロールブロックと呼ばれる別のメモリ領域がヒープから取られる。 よって、格納するオブジェクトと合わせて 2回のメモリ確保が行われることになる。(
まとめ
- C++11 で導入されたスマートポインタの使い分けについて書いてみた。昔はもっといろいろ迷っていた気がするので、たぶんもっと挙げるべき例があるのだと思う。こんなときは ? みたいなコメントぜひお待ちしています。