std::make_shared から private コンストラクタを呼び出す

std::make_shared で private コンストラクタを呼ぶには,関数内クラスと継承を利用すれば良い.

class hoge {
  hoge();  // private ctor

 public:
  static std::shared_ptr<hoge> create() {
    // return std::make_shared<hoge>();  // こう書きたいが,コンパイルエラー
    // 代わりに関数内クラスを利用する
    struct impl : hoge {
      impl() : hoge() {}
    };
    auto p = std::make_shared<impl>();
    return std::move(p);
  }
};

継承ではなく包含を使うことも可能だし,関数内クラスではなくクラス内クラスを使うことも出来る.

class hoge {
  hoge();  // private ctor

  struct create_helper {
    hoge x;
    // クラス内クラスだとテンプレートにできるのでテンプレートで書いてみる
    template<class... Args>
    explicit create_helper(Args&&... args)
        : x(std::forward<Args>(args)...) {
    }
  };

 public:
  static std::shared_ptr<hoge> create() {
    auto p = std::make_shared<create_helper>();
    return std::shared_ptr<hoge>(std::move(p), &p->x);
  }
};

[7/5, 2014 追記] クラス内クラスを使う場合は,クラス内クラスを定義する位置に気をつける必要がある.

class hoge {
  hoge();  // private ctor
  struct create_helper;

 public:
  static std::shared_ptr<hoge> create();
};

struct hoge::create_helper {
  hoge x;
  // クラス内クラスだとテンプレートにできるのでテンプレートで書いてみる
  template<class... Args>
  explicit create_helper(Args&&... args)
      : x(std::forward<Args>(args)...) {
  }
};

std::shared_ptr<hoge> hoge::create() {
  auto p = std::make_shared<create_helper>();
  return std::shared_ptr<hoge>(std::move(p), &p->x);
}

注意点として,包含を使った場合,現行の仕様・実装では std::enable_shared_from_this が上手く働かないため,
shared_from_this を使うのであれば継承を使う必要がある.

#include <memory>

class hoge : public std::enable_shared_from_this<hoge> {
  hoge(){}  // private ctor
  struct create_helper;

 public:
  static std::shared_ptr<hoge> create();
};

struct hoge::create_helper {
  hoge x;
  // クラス内クラスだとテンプレートにできるのでテンプレートで書いてみる
  template<class... Args>
  explicit create_helper(Args&&... args)
      : x(std::forward<Args>(args)...) {
  }
};

std::shared_ptr<hoge> hoge::create() {
  auto p = std::make_shared<create_helper>();
  return std::shared_ptr<hoge>(std::move(p), &p->x);
}

int main() {
  auto p = hoge::create();
  p->shared_from_this();  // error!
}

http://melpon.org/wandbox/permlink/Rt7WAdfEOsN8jR1w


とまぁ,細かい注意点はいろいろあるが,要するに, private コンストラクタが見えているところに補助クラスを定義し,そいつを make_shared した後,補助クラス内部にある目当てのオブジェクトへの shared_ptr を作ればよいのである


以下,補足説明.


そもそも std::make_shared は,「オブジェクトを new して std::shared_ptr に格納する」という処理を,安全かつ効率的に行う関数テンプレートである.

struct hoge {
  // ...
};

void f(std::shared_ptr<hoge> p1, std::shared_ptr<hoge> p2);

int main() {
  auto p = std::make_shared<hoge>();  // これは
  // std::shared_ptr<hoge> p(new hoge());  // これと同じだが,より効率的

  f(std::shared_ptr<hoge>(new hoge()), std::shared_ptr<hoge>(new hoge()));  // メモリリークの可能性あり
  f(std::make_shared<hoge>(), std::make_shared<hoge>());  // 例外安全
}


しかし,この make_shared は,呼び出したいコンストラクタが private で定義されていた場合,たとえクラス内からであろうと,直接 make_shared 経由でオブジェクトを構築させることはできない.

// shared_ptr に格納されることが前提のクラス
class hoge {
  hoge();  // private ctor

 public:
  static std::shared_ptr<hoge> create() {
    return std::shared_ptr<hoge>(new hoge());  // make_shared<hoge>() だとコンパイルエラー
  }
};

これは,たとえ make_shared がクラス内で使われていようとも, make_shared 関数の中でコンストラクタが呼ばれる文脈は,構築したいクラス(先程の例では hoge)とは無関係のものであるためだ.


ならば friend 関数宣言を使えば良い,と思うかもしれないが,

  • コンストラクタ呼び出しが std::make_shared 直下で行われているとは限らない(make_shared の内部で実装用関数を呼んでいるかもしれない
  • std::make_shared のシグネチャが規格通りとは限らない(enable_if などを使っている場合にはシグネチャは一致しなくなる

といった理由で, friend 関数は相応しくないし,仮に friend 関数にすることができても,その場合 クラス外部からも make_shared できてしまうため,モジュール化を壊してしまう.


そのため,今回 紹介したような,補助クラスを用いた小技が必要になってくるのである.