保護されたコンストラクターまたはプライベートコンストラクターのみを含むクラスで:: std :: make_sharedを呼び出すにはどうすればよいですか?


187

動作しないこのコードがありますが、その意図は明らかだと思います:

testmakeshared.cpp

#include <memory>

class A {
 public:
   static ::std::shared_ptr<A> create() {
      return ::std::make_shared<A>();
   }

 protected:
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

しかし、コンパイルするとこのエラーが発生します。

g++ -std=c++0x -march=native -mtune=native -O3 -Wall testmakeshared.cpp
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:52:0,
                 from /usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/memory:86,
                 from testmakeshared.cpp:1:
testmakeshared.cpp: In constructor std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator<A>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’:
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:518:8:   instantiated from std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:986:35:   instantiated from std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:313:64:   instantiated from std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<A>, _Args = {}, _Tp = A]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:531:39:   instantiated from std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator<A>, _Args = {}]’
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr.h:547:42:   instantiated from std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = A, _Args = {}]’
testmakeshared.cpp:6:40:   instantiated from here
testmakeshared.cpp:10:8: error: A::A()’ is protected
/usr/lib/gcc/x86_64-redhat-linux/4.6.1/../../../../include/c++/4.6.1/bits/shared_ptr_base.h:400:2: error: within this context

Compilation exited abnormally with code 1 at Tue Nov 15 07:32:58

このメッセージは基本的に、テンプレートのインスタンス化スタックの下位にあるいくつかのランダムなメソッドが、::std::make_shared保護されているためコンストラクタにアクセスできないことを示しています。

しかし、私は本当に両方を使用::std::make_sharedし、誰もがこのクラスのオブジェクトを作成しないようにしたいのです::std::shared_ptr。これを達成する方法はありますか?


コンストラクターを必要とする関数を友人として深く掘り下げることができますが、移植性はありません。
ダニ

@ダニ:ええ、それはポータブルなソリューションを持っているといいでしょう。しかし、それはうまくいくでしょう。
40:40に全知

回答:


109

この答えはおそらくより良く、おそらく私が受け入れるだろう。しかし、私は醜いメソッドも思いつきましたが、依然としてすべてをインラインにして、派生クラスを必要としません。

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

::std::shared_ptr<A> foo()
{
   return A::create();
}

::std::shared_ptr<A> bar()
{
   return A::create("George", 5);
}

::std::shared_ptr<A> errors()
{
   ::std::shared_ptr<A> retval;

   // Each of these assignments to retval properly generates errors.
   retval = A::create("George");
   retval = new A(A::this_is_private{0});
   return ::std::move(retval);
}

2017-01-06を編集:私はこれを変更して、このアイデアが引数を取るコンストラクターに明確かつ簡単に拡張可能であることを明確にしました。


14
実際、私はキーとしてのみ使用されるこれらの無意味な構造の大ファンです。私はこれをLucの解決策よりも好みますが、それは私の継承に対する偏見かもしれません。
Matthieu M.

2
同意しました、私もこれが好きです。
ildjarn

3
@Berkus:protectedではなくにし​​てくださいprivate。そして、「それ」によって、私はthis_is_privateクラスを参照しています。おそらくそのような場合には名前を変更する必要があります。私は通常constructor_access、コードでそれを呼び出します。
dalle

1
悲しいことに、コンストラクターが実際のパラメーターを取る場合、これは機能しません。この場合は{}、型名にアクセスせずに、プライベートタグを渡すだけです(g ++ 4.9.0でテスト済み)。実際のパラメーターがないとA{} から構築しようとしますが、理由はわかりませんが失敗します。this_is_privateコンストラクターをプライベートにし、それを作成する静的メソッドを提供すると、メンバー関数のシグネチャの型をリークしない限り、外部からこのメソッドにアクセスする方法がないため、修正できると思います。
ステファン14

3
ステファン、this_is_private私的な俳優を与えると、クラスAを友達にできます。抜け穴をふさいでいるようです。
Steven Kramer

78

std::make_shared20.7.2.2.6でshared_ptrを作成するための要件[util.smartptr.shared.create]のパラグラフ1を見てください。

必須:式は::new (pv) T(std::forward<Args>(args)...)pvvoid*を持ち、型のオブジェクトを保持するのに適したストレージを指しますが、適切にT形成されている必要があります。Aアロケータとする(17.6.3.5)。のコピーコンストラクタとデストラクタはA例外をスローしません。

その表現に関して無条件に要件が規定されており、スコープなどは考慮されていないので、友情のようなトリックは正解だと思います。

単純な解決策は、から派生することですA。これはA、インターフェイスやポリモーフィック型を作成する必要はありません。

// interface in header
std::shared_ptr<A> make_a();

// implementation in source
namespace {

struct concrete_A: public A {};

} // namespace

std::shared_ptr<A>
make_a()
{
    return std::make_shared<concrete_A>();
}

1
ああ、それは非常に賢い答えで、おそらく私が考えていた別の答えよりも良いでしょう。
方位

ただし、1つの質問として、shared_ptrはAを削除し、concrete_Aは削除しないので、問題が発生することはありませんか?
全方位

8
ああ、それshared_ptrはインスタンス化の時に削除者を保存するからです、そしてあなたがmake_shared削除者を使っているなら絶対に正しいタイプを使わなければなりません。
23:00に全知

1
@LucDantonの質問はインターフェースに関するものではありません。タイトルは彼が民間の俳優も求めていることを示唆しています。また、それが私がとにかくこの質問にいる理由です。プライベートアクターと生のポインターを返すcreateメソッドを持つmachiavelliクラスを持ついくつかの古いコード、そしてそれらをスマートポインターに変換しようとしています。
zahir 2015

2
私はこのアプローチ(自分で使用)が好きですが、仮想デストラクタが必要です。これは、引数を持つコンストラクターにまで拡張されます(パススルーコンストラクターを提供するだけです)。また、プライベートではなく保護を使用している場合は、ヘッダーのユーザーから完全に見えなくすることができます。
ジョースティール

69

おそらく最も簡単なソリューションです。Mohit Aronによる以前の回答に基づいており、dlfの提案を取り入れています。

#include <memory>

class A
{
public:
    static std::shared_ptr<A> create()
    {
        struct make_shared_enabler : public A {};

        return std::make_shared<make_shared_enabler>();
    }

private:
    A() {}  
};

5
場合はA、デフォルト以外のコンストラクタを持っています。また、それらを公開する必要がありますstruct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };。これにより、すべてのプライベートコンストラクターがコンストラクターとしてA表示さmake_shared_enablerれます。コンストラクターの継承機能(using A::A;)を使用しても、コンストラクターはまだプライベートであるため、ここでは役に立たないようです。
anton_rh

2
@anton_rh:内部クラスにテンプレート引数を追加することはできません。こちらをご覧ください
bobbel 2016

3
うーん...あなたは正しいようです。私の場合、構造体はローカルではなく、プライベート構造体でした:class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }。ここcpp.sh/65qbrを参照してください
anton_rh

これはうまくいきます。これを継承可能なプロパティにする可能性はあるので、このパターンを複数回繰り返す必要はありませんか?特に、デフォルト以外のコンストラクタを公開するバージョンは、私にとって非常に興味深いでしょう。デフォルトのバージョンでは、「単純に」、クラスを継承するクラスでAを置き換える構文構造が必要になります。私はそのようなことは何も知りませんが、それが存在することを知って驚くことはありません...
Kjeld Schmidt

30

これのためのきちんとした解決策はここにあります:

#include <memory>

class A {
   public:
     static shared_ptr<A> Create();

   private:
     A() {}

     struct MakeSharedEnabler;   
 };

struct A::MakeSharedEnabler : public A {
    MakeSharedEnabler() : A() {
    }
};

shared_ptr<A> A::Create() {
    return make_shared<MakeSharedEnabler>();
}

3
私はこれが好き。MakeSharedEnabler内部でローカルに定義することで、少し単純にすることができますA::Create()
dlf 2014

素晴らしいアイデアMohitは、私を大いに助けてくれました。
Jnana

12

これはどう?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}

13
それはうまくいきます。しかし::std::make_shared、Shared_ptrを単に何かにする以上の機能があります。参照カウントをオブジェクトとともに割り当て、それらが互いに近くに配置されるようにします。本当に、本当に使いたいです::std::make_shared
23:05に全知

削除された割り当ておよびコピーオペレーターはこれを禁止します
ダニ

7
これは、質問が実際に求めていたものではありませんが、本当に最も簡単なアプローチです。make_sharedにはいくつかの優れた特性があり、可能な限り使用しようとしていますが、この状況では、make_sharedの実行時のパフォーマンス上の利点が、実際に使用するために必要な追加のコードの複雑さと式を上回らないようです。make_sharedのパフォーマンスが本当に必要な場合は、おかしくなりますが、shared_ptrのコンストラクターを使用するだけの単純さを見逃さないでください。
ケビン

ただし、メモリリークに注意してください...この質問を参照してくださいstackoverflow.com/a/14837300/2149539
dgmz

12
struct A {
public:
  template<typename ...Arg> std::shared_ptr<A> static create(Arg&&...arg) {
    struct EnableMakeShared : public A {
      EnableMakeShared(Arg&&...arg) :A(std::forward<Arg>(arg)...) {}
    };
    return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
  }
  void dump() const {
    std::cout << a_ << std::endl;
  }
private:
  A(int a) : a_(a) {}
  A(int i, int j) : a_(i + j) {}
  A(std::string const& a) : a_(a.size()) {}
  int a_;
};

これは、Luc Dantonの回答とほぼ同じですが、ローカルクラスに変換するのはいい感じです。コードに付随するいくつかの説明がこれをより良い答えにすることができます。

通常、このような小さな関数をccファイルではなくヘッダーファイルに記述します。次に、実際には、#define SharedPtrCreate(T)template <typename ... Arg> .....
alpha

いい答えだ。IMPLEMENT_CREATE_SHARED(ClassName)のようなマクロにそれを入れさえしました
ivan.ukr

8

私はすでに提供された回答が気に入らなかったので、検索することに決め、以前の回答ほど一般的ではない解決策を見つけましたが、私はそれをより好んでいます(tm)。振り返ってみると、それはオムニファリウスによって提供されたものよりもそれほど良くはありませんが、それを好きな他の人々もいるでしょう:)

これは私が発明したものではありませんが、Jonathan Wakely(GCC開発者)のアイデアです。

残念ながら、std :: allocate_shared実装の小さな変更に依存しているため、すべてのコンパイラーで機能するわけではありません。ただし、この変更は標準ライブラリの更新案として提案されているため、将来すべてのコンパイラでサポートされる可能性があります。GCC 4.7で動作します。

C ++標準ライブラリワーキンググループの変更リクエストはこちら:http : //lwg.github.com/issues/lwg-active.html#2070

使用例のGCCパッチはこちらです:http : //old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

このソリューションは、プライベートコンストラクターを持つクラスのフレンドとして宣言されたカスタムアロケーターで(std :: make_sharedの代わりに)std :: allocate_sharedを使用するという考えに基づいて機能します。

OPの例は次のようになります。

#include <memory>

template<typename Private>
struct MyAlloc : std::allocator<Private>
{
    void construct(void* p) { ::new(p) Private(); }
};

class A {
    public:
        static ::std::shared_ptr<A> create() {
            return ::std::allocate_shared<A>(MyAlloc<A>());
        }

    protected:
        A() {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;

        friend struct MyAlloc<A>;
};

int main() {
    auto p = A::create();
    return 0;
}

私が取り組んでいるユーティリティに基づくより複雑な例。これでは、Lucのソリューションを使用できませんでした。しかし、Omnifariusによるものは適応できます。前の例では、誰でもMyAllocを使用してAオブジェクトを作成できますが、このオブジェクトでは、create()メソッド以外にAまたはBを作成する方法はありません。

#include <memory>

template<typename T>
class safe_enable_shared_from_this : public std::enable_shared_from_this<T>
{
    public:
    template<typename... _Args>
        static ::std::shared_ptr<T> create(_Args&&... p_args) {
            return ::std::allocate_shared<T>(Alloc(), std::forward<_Args>(p_args)...);
        }

    protected:
    struct Alloc : std::allocator<T>
    {  
        template<typename _Up, typename... _Args>
        void construct(_Up* __p, _Args&&... __args)
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    };
    safe_enable_shared_from_this(const safe_enable_shared_from_this&) = delete;
    safe_enable_shared_from_this& operator=(const safe_enable_shared_from_this&) = delete;
};

class A : public safe_enable_shared_from_this<A> {
    private:
        A() {}
        friend struct safe_enable_shared_from_this<A>::Alloc;
};

class B : public safe_enable_shared_from_this<B> {
    private:
        B(int v) {}
        friend struct safe_enable_shared_from_this<B>::Alloc;
};

int main() {
    auto a = A::create();
    auto b = B::create(5);
    return 0;
}

6

理想的には、完璧なソリューションにはC ++標準への追加が必要だと思います。アンドリューシェプラーは次のことを提案しています。

(スレッド全体については、ここに移動してください

boost :: iterator_core_accessからアイデアを借りることができます。新しいクラスを提案しますstd::shared_ptr_accessパブリックメンバーも保護メンバーもないそれをstd :: make_shared(args ...)およびstd :: alloc_shared(a、args ...)に指定するために、式:: new(pv)T (forward(args)...)およびptr->〜T()は、std :: shared_ptr_accessのコンテキストで整形式でなければなりません。

std :: shared_ptr_accessの実装は次のようになります。

namespace std {
    class shared_ptr_access
    {
        template <typename _T, typename ... _Args>
        static _T* __construct(void* __pv, _Args&& ... __args)
        { return ::new(__pv) _T(forward<_Args>(__args)...); }

        template <typename _T>
        static void __destroy(_T* __ptr) { __ptr->~_T(); }

        template <typename _T, typename _A>
        friend class __shared_ptr_storage;
    };
}

使用法

上記が標準に追加された場合は、次のようにします。

class A {
public:
   static std::shared_ptr<A> create() {
      return std::make_shared<A>();
   }

 protected:
   friend class std::shared_ptr_access;
   A() {}
   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};

これも標準に対する重要な追加のように思える場合は、リンクされたisocpp Googleグループに2セントを追加してください。


1
これは標準への良い追加だと思いますが、私が時間をかけてGoogleグループに参加してコメントし、そのグループとコメントに注意を払うことは十分に重要ではありません。:-)
2018

4

このスレッドはかなり古いことに気づきましたが、継承やコンストラクターへの追加の引数を必要としない答えを見つけたので、他では見ることができませんでした。ただし、移植性はありません。

#include <memory>

#if defined(__cplusplus) && __cplusplus >= 201103L
#define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator<test>::construct<test>(test*);
#elif defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) && _MSC_VER >= 1800
#define ALLOW_MAKE_SHARED(x) friend class std::_Ref_count_obj;
#else
#error msc version does not suport c++11
#endif
#else
#error implement for platform
#endif

class test {
    test() {}
    ALLOW_MAKE_SHARED(test);
public:
    static std::shared_ptr<test> create() { return std::make_shared<test>(); }

};
int main() {
    std::shared_ptr<test> t(test::create());
}

私はWindowsとLinuxでテストしましたが、プラットフォームによっては微調整が必​​要な場合があります。


1
移植性がないため、私はそれを-1にしたいと思っています。他の回答(特に「主要なクラス」の回答)はかなりエレガントで、移植性のない回答は非常に醜いです。移植性のない回答を使用する理由は考えられません。それはより速くないか、そのようなものではありません。
Omnifarious 2016

@Omnifariousそれは確かに移植不可能であり、私はお勧めしませんが、これは実際には意味的に最も正しい解決策だと思います。では私の答えは、私は追加の提案にリンクしてstd::shared_ptr_access、シンプルでポータブルな方法で上記を行うことが可能と見ることができる標準に。
Boris Dalstein、2018

3

一緒に動作する2つの厳密に関連するクラスAとBがある場合に発生する、より厄介で興味深い問題があります。

Aが「マスタークラス」で、Bが「スレーブ」であるとします。Bのインスタンス化をAだけに制限したい場合は、Bのコンストラクターをプライベートにし、BをAにフレンド化します。

class B
{
public:
    // B your methods...

private:
    B();
    friend class A;
};

残念ながらstd::make_shared<B>()、のメソッドから呼び出すAと、コンパイラーB::B()はプライベートであると文句を言います。

これに対する私の解決策は、プライベートコンストラクターを持ち、とフレンドであるのPassようなパブリックダミークラスをnullptr_t内部に作成し、のBコンストラクターをフレンドにしてA、このBコンストラクターをパブリックにPassして引数に追加することです。

class B
{
public:
  class Pass
  {
    Pass() {}
    friend class A;
  };

  B(Pass, int someArgument)
  {
  }
};

class A
{
public:
  A()
  {
    // This is valid
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

class C
{
public:
  C()
  {
    // This is not
    auto ptr = std::make_shared<B>(B::Pass(), 42);
  }
};

3

引数を取るコンストラクタも有効にしたい場合、これは少し役立つかもしれません。

#include <memory>
#include <utility>

template<typename S>
struct enable_make : public S
{
    template<typename... T>
    enable_make(T&&... t)
        : S(std::forward<T>(t)...)
    {
    }
};

class foo
{
public:
    static std::unique_ptr<foo> create(std::unique_ptr<int> u, char const* s)
    {
        return std::make_unique<enable_make<foo>>(std::move(u), s);
    }
protected:
    foo(std::unique_ptr<int> u, char const* s)
    {
    }
};

void test()
{
    auto fp = foo::create(std::make_unique<int>(3), "asdf");
}

3

[編集]標準化されたstd::shared_ptr_access<>提案について上記のスレッドを読みました。中には修正std::allocate_shared<>とその使用例を記した回答がありました。以下のファクトリテンプレートに適合させ、gcc C ++ 11/14/17でテストしました。それstd::enable_shared_from_this<>も同様に動作するので、この回答の元の解決策よりも明らかに好ましいでしょう。ここにあります...

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        return std::allocate_shared<T>(Alloc<T>(), std::forward<A>(args)...);
    }
private:
    template<typename T>
    struct Alloc : std::allocator<T> {
        template<typename U, typename... A>
        void construct(U* ptr, A&&... args) {
            new(ptr) U(std::forward<A>(args)...);
        }
        template<typename U>
        void destroy(U* ptr) {
            ptr->~U();
        }
    };  
};

class X final : public std::enable_shared_from_this<X> {
    friend class Factory;
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(int) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto p1 = Factory::make_shared<X>(42);
    auto p2 = p1->shared_from_this();
    std::cout << "p1=" << p1 << "\n"
              << "p2=" << p2 << "\n"
              << "count=" << p1.use_count() << "\n";
}

[元]共有ポインターエイリアシングコンストラクターを使用した解決策を見つけました。これにより、ctorとdtorの両方をプライベートにできるほか、最終的な指定子を使用できます。

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        return std::shared_ptr<T>(ptr, &ptr->type);
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = Factory::make_shared<X>(42);
}

上記のアプローチはstd::enable_shared_from_this<>、イニシャルstd::shared_ptr<>がタイプ自体ではなくラッパーに対するものであるため、うまく機能しないことに注意してください。ファクトリと互換性のある同等のクラスでこれに対処できます...

#include <iostream>
#include <memory>

template<typename T>
class EnableShared {
    friend class Factory;  // factory access
public:
    std::shared_ptr<T> shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared<T>& operator=(const EnableShared<T>&) { return *this; }  // no slicing
private:
    std::weak_ptr<T> weak;
};

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        auto alt = std::shared_ptr<T>(ptr, &ptr->type);
        assign(std::is_base_of<EnableShared<T>, T>(), alt);
        return alt;
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template<typename T>
    static void assign(std::true_type, const std::shared_ptr<T>& ptr) {
        ptr->weak = ptr;
    }
    template<typename T>
    static void assign(std::false_type, const std::shared_ptr<T>&) {}
};

class X final : public EnableShared<X> {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}

最後に、誰かがclangがFactory :: Typeが友達として使用されたときにプライベートであることに不満を言ったと言ったので、それが事実ならそれを公開してください。露出しても害はありません。


3

私も同じ問題を抱えていましたが、保護されたコンストラクターに引数を渡す必要があるため、既存の回答はどれも満足のいくものではありませんでした。さらに、これをいくつかのクラスに対して行う必要があり、それぞれが異なる引数を取ります。

そのために、すべて同じような方法を使用する既存の回答のいくつかに基づいて、私はこの小さなナゲットを提示します。

template < typename Object, typename... Args >
inline std::shared_ptr< Object >
protected_make_shared( Args&&... args )
{
  struct helper : public Object
  {
    helper( Args&&... args )
      : Object{ std::forward< Args >( args )... }
    {}
  };

  return std::make_shared< helper >( std::forward< Args >( args )... );
}

1

問題の根本は、フレンドまたはフレンドが、コンストラクタに対してより低レベルの呼び出しを行う場合、フレンドもフレンドにする必要があることです。std :: make_sharedは、実際にコンストラクターを呼び出している関数ではないため、フレンドリ化しても違いはありません。

class A;
typedef std::shared_ptr<A> APtr;
class A
{
    template<class T>
    friend class std::_Ref_count_obj;
public:
    APtr create()
    {
        return std::make_shared<A>();
    }
private:
    A()
    {}
};

std :: _ Ref_count_objは実際にはコンストラクターを呼び出しているため、フレンドである必要があります。少しあいまいなので、マクロを使用します

#define SHARED_PTR_DECL(T) \
class T; \
typedef std::shared_ptr<T> ##T##Ptr;

#define FRIEND_STD_MAKE_SHARED \
template<class T> \
friend class std::_Ref_count_obj;

その後、クラス宣言はかなり単純に見えます。必要に応じて、ptrとクラスを宣言するための単一のマクロを作成できます。

SHARED_PTR_DECL(B);
class B
{
    FRIEND_STD_MAKE_SHARED
public:
    BPtr create()
    {
        return std::make_shared<B>();
    }
private:
    B()
    {}
};

これは実際には重要な問題です。保守可能で移植可能なコードを作成するには、実装をできるだけ隠す必要があります。

typedef std::shared_ptr<A> APtr;

スマートポインターの処理方法を少し隠します。必ずtypedefを使用する必要があります。ただし、常にmake_sharedを使用して作成する必要がある場合は、目的を達成できません。

上記の例では、クラスを使用するコードにスマートポインターコンストラクターを使用するように強制します。つまり、スマートポインターの新しいフレーバーに切り替えると、クラス宣言が変更され、終了する可能性が高くなります。次のボスやプロジェクトがstl、boostなどを使用して、いつか変更することを想定しないでください。

ほぼ30年間これを行っていましたが、何年も前に間違って行われていた場合、これを修復するために時間と痛みと副作用に大きな代償を払っています。


2
std::_Ref_count_obj実装の詳細です。つまり、このソリューションは今のところプラットフォームで機能する可能性があります。ただし、他のユーザーには機能しない可能性があり、コンパイラが更新されるたびに、またはコンパイルフラグを変更しただけでも機能しなくなる可能性があります。
フランソワ・アンドリュー

-3

あなたはこれを使うことができます:

class CVal
{
    friend std::shared_ptr<CVal>;
    friend std::_Ref_count<CVal>;
public:
    static shared_ptr<CVal> create()
    {
        shared_ptr<CVal> ret_sCVal(new CVal());
        return ret_sCVal;
    }

protected:
    CVal() {};
    ~CVal() {};
};

1
使用しませんstd::make_shared
ブライアン

-3
#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
private:
    A(){}
    explicit A(int a):m_a(a){}
public:
    template <typename... Args>
    static std::shared_ptr<A> create(Args &&... args)
    {
        class make_shared_enabler : public A
        {
        public:
            make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
        };
        return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
    }

    int val() const
    {
        return m_a;
    }
private:
    int m_a=0;
};

int main(int, char **)
{
    std::shared_ptr<A> a0=A::create();
    std::shared_ptr<A> a1=A::create(10);
    std::cout << a0->val() << " " << a1->val() << std::endl;
    return 0;
}

これはこの回答の複製です:stackoverflow.com/a/27832765/167958
Omnifarious
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.