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 できてしまうため,モジュール化を壊してしまう.
そのため,今回 紹介したような,補助クラスを用いた小技が必要になってくるのである.