C++でunique_ptrとshared_ptrを使い分ける目安

C++でスマートポインタを使うとき、unique_ptrshared_ptr のどちらにするか迷うことがあります。

最初に見るのは所有権です。

  • 1箇所だけが所有する -> std::unique_ptr
  • 複数箇所で所有を共有する -> std::shared_ptr

迷ったら unique_ptr から考える方が整理しやすいです。

unique_ptrを使う場面

std::unique_ptr は、所有者が1つだけのポインタです。

#include <memory>

struct User {
    int id;
};

std::unique_ptr<User> makeUser() {
    return std::make_unique<User>(User{1});
}

戻り値として返す場合も、所有権が移動するだけなので扱いやすいです。

auto user = makeUser();

コピーはできません。

auto a = std::make_unique<User>();
// auto b = a; // error

所有者が1つだけなので、いつ破棄されるかも追いやすくなります。

shared_ptrを使う場面

std::shared_ptr は、参照カウントで所有を共有します。

#include <memory>
#include <vector>

auto user = std::make_shared<User>(User{1});

std::vector<std::shared_ptr<User>> users;
users.push_back(user);

複数の場所が同じオブジェクトを保持し、最後の shared_ptr が消えたときに破棄されます。

便利ですが、ライフタイムが見えづらくなりやすいです。

shared_ptrを避けたい場面

「とりあえずshared_ptr」にすると、誰が所有しているのか分からなくなります。

たとえば、単に関数内で使うだけなら参照で十分です。

void printUser(const User& user) {
    // 読むだけ
}

所有しないなら、スマートポインタを渡す必要はありません。

循環参照に注意する

shared_ptr 同士が互いに持ち合うと、参照カウントが0にならず破棄されません。

struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
};

こういう場合は、片方を std::weak_ptr にします。

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;
};

まとめ

スマートポインタは所有権で選ぶと整理しやすいです。

  • 基本は std::unique_ptr
  • 複数箇所で所有する必要があるときだけ std::shared_ptr
  • 所有しない引数は参照やポインタで渡す
  • shared_ptr の循環参照には weak_ptr

まず unique_ptr で表現できないかを見る。そこから外れる場合に shared_ptr を使う、くらいが扱いやすいです。

関連記事