Wie rufe ich :: std :: make_shared für eine class mit nur geschützten oder privaten Konstruktoren auf?

Ich habe diesen Code, der nicht funktioniert, aber ich denke, die Absicht ist klar:

testmakeshared.cpp

#include  class A { public: static ::std::shared_ptr create() { return ::std::make_shared(); } protected: A() {} A(const A &) = delete; const A &operator =(const A &) = delete; }; ::std::shared_ptr foo() { return A::create(); } 

Aber ich bekomme diesen Fehler, wenn ich es kompiliere:

 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::_Sp_counted_ptr_inplace(_Alloc) [with _Tp = A, _Alloc = std::allocator, __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::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator, _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::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator, _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::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator, _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 std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = A, _Alloc = std::allocator, _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 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 

Diese Nachricht besagt grundsätzlich, dass einige zufällige Methoden weit unten im Template-Instanziierungs-Stack von ::std::make_shared nicht auf den Konstruktor ::std::make_shared können, da dieser geschützt ist.

Aber ich möchte wirklich beide ::std::make_shared und verhindern, dass irgendjemand ein Objekt dieser class erzeugt, auf das nicht ein ::std::shared_ptr . Gibt es einen Weg, dies zu erreichen?

    Betrachten Sie die Anforderungen für std::make_shared in 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], Absatz 1:

    Benötigt: Der Ausdruck ::new (pv) T(std::forward(args)...) , wobei pv den Typ void* und auf Speicher verweist, der geeignet ist, ein Objekt vom Typ T zu halten, soll wohlgeformt sein . A ist ein Allokator (17.6.3.5). Der Kopierkonstruktor und der Destruktor von A dürfen keine Ausnahmen auslösen.

    Da die Anforderung in Bezug auf diesen Ausdruck bedingungslos spezifiziert ist und Dinge wie der scope nicht berücksichtigt werden, denke ich, dass Tricks wie Freundschaft gleich sind.

    Eine einfache Lösung ist von A abzuleiten. Dies erfordert nicht, dass A eine Schnittstelle oder sogar ein polymorpher Typ ist.

     // interface in header std::shared_ptr make_a(); // implementation in source namespace { struct concrete_A: public A {}; } // namespace std::shared_ptr make_a() { return std::make_shared(); } 

    Möglicherweise die einfachste Lösung. Basierend auf der vorherigen Antwort von Mohit Aron und dem Vorschlag von dlf.

     #include  class A { public: static std::shared_ptr create() { struct make_shared_enabler : public A {}; return std::make_shared(); } private: A() {} }; 

    Wie wäre es damit?

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

    Da mir die bereits bereitgestellten Antworten nicht gefallen haben, habe ich mich entschieden, nach einer Lösung zu suchen, die nicht so allgemein ist wie die vorherigen Antworten, aber ich mag sie besser ™. Im Nachhinein ist es nicht viel schöner als das von Omnifarius, aber es könnte auch andere Leute geben, die es mögen 🙂

    Dies ist nicht von mir erfunden, aber es ist die Idee von Jonathan Wakely (GCC-Entwickler).

    Leider funktioniert es nicht mit allen Compilern, da es auf einer kleinen Änderung in der Implementierung von std :: allocate_shared beruht. Aber diese Änderung ist jetzt ein vorgeschlagenes Update für die Standardbibliotheken, sodass sie zukünftig von allen Compilern unterstützt wird. Es funktioniert auf GCC 4.7.

    Die Änderungsanforderung für die C ++ – Standardbibliotheksarbeitsgruppe lautet hier: http://lwg.github.com/issues/lwg-active.html#2070

    Der GCC-Patch mit einer Beispielverwendung ist hier: http://old.nabble.com/Re%3A–v3–Implement-pointer_traits-and-allocator_traits-p31723738.html

    Die Lösung basiert auf der Idee, std :: allocate_shared (anstelle von std :: make_shared) mit einem benutzerdefinierten Zuordner zu verwenden, der der class mit dem privaten Konstruktor als Freund deklariert wird.

    Das Beispiel aus dem OP würde so aussehen:

     #include  template struct MyAlloc : std::allocator { void construct(void* p) { ::new(p) Private(); } }; class A { public: static ::std::shared_ptr create() { return ::std::allocate_shared(MyAlloc()); } protected: A() {} A(const A &) = delete; const A &operator =(const A &) = delete; friend struct MyAlloc; }; int main() { auto p = A::create(); return 0; } 

    Ein komplexeres Beispiel, das auf dem Dienstprogramm basiert, an dem ich gerade arbeite. Damit konnte ich Lucs Lösung nicht nutzen. Aber die von Omnifarius könnte angepasst werden. Nicht dass, während im vorherigen Beispiel jeder ein A-Objekt mit dem MyAlloc in diesem erstellen kann, gibt es keine Möglichkeit, neben der create () -Methode auch A oder B zu erstellen.

     #include  template class safe_enable_shared_from_this : public std::enable_shared_from_this { public: template static ::std::shared_ptr create(_Args&&... p_args) { return ::std::allocate_shared(Alloc(), std::forward<_args>(p_args)...); } protected: struct Alloc : std::allocator { template 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 { private: A() {} friend struct safe_enable_shared_from_this::Alloc; }; class B : public safe_enable_shared_from_this { private: B(int v) {} friend struct safe_enable_shared_from_this::Alloc; }; int main() { auto a = A::create(); auto b = B::create(5); return 0; } 

    Ich weiß, dass dieser Thread ziemlich alt ist, aber ich habe eine Antwort gefunden, die keine inheritance oder zusätzliche Argumente für den Konstruktor erfordert, die ich anderswo nicht sehen konnte. Es ist jedoch nicht übertragbar:

     #include  #if defined(__cplusplus) && __cplusplus >= 201103L #define ALLOW_MAKE_SHARED(x) friend void __gnu_cxx::new_allocator::construct(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 create() { return std::make_shared(); } }; int main() { std::shared_ptr t(test::create()); } 

    Ich habe auf Windows und Linux getestet, muss es für verschiedene Plattformen optimieren.

    Wenn Sie auch einen Konstruktor aktivieren möchten, der Argumente übernimmt, kann das ein wenig helfen.

     #include  #include  template struct enable_make : public S { template enable_make(T&&... t) : S(std::forward(t)...) { } }; class foo { public: static std::unique_ptr create(std::unique_ptr u, char const* s) { return std::make_unique>(std::move(u), s); } protected: foo(std::unique_ptr u, char const* s) { } }; void test() { auto fp = foo::create(std::make_unique(3), "asdf"); } 

    Es gibt ein haarigeres und interessanteres Problem, das auftritt, wenn Sie zwei streng verwandte classn A und B haben, die zusammenarbeiten.

    Sage A ist die “Meisterklasse” und B sein “Sklave”. Wenn Sie die Instanziierung von B nur auf A beschränken möchten, würden Sie den Konstruktor von B privat machen und Freund B mit A so

     class B { public: // B your methods... private: B(); friend class A; }; 

    Leider ruft der Aufruf von std::make_shared() von einer Methode von A den Compiler auf, dass B::B() privat ist.

    Meine Lösung dazu ist, eine öffentliche Pass Dummy-class (genau wie nullptr_t ) in B zu erstellen, die einen privaten Konstruktor hat und mit A nullptr_t ist und B Konstruktor public macht und Pass zu seinen Argumenten hinzufügt.

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

    Idealerweise denke ich, dass die perfekte Lösung Ergänzungen zum C ++ – Standard erfordern würde. Andrew Schepler schlägt folgendes vor:

    (Geh hier für den ganzen Thread)

    Wir können eine Idee von boost :: iterator_core_access ausleihen. Ich schlage eine neue class std::shared_ptr_access ohne public oder protected members vor und für std :: make_shared (args …) und std :: alloc_shared (a, args …) die Ausdrücke :: new (pv) T (forward (args) …) und ptr-> ~ T () müssen im Kontext von std :: shared_ptr_access wohlgeformt sein.

    Eine Implementierung von std :: shared_ptr_access könnte folgendermaßen aussehen:

     namespace std { class shared_ptr_access { template  static _T* __construct(void* __pv, _Args&& ... __args) { return ::new(__pv) _T(forward<_args>(__args)...); } template  static void __destroy(_T* __ptr) { __ptr->~_T(); } template  friend class __shared_ptr_storage; }; } 

    Verwendung

    Wenn / ob das obige zum Standard hinzugefügt wird, würden wir einfach tun:

     class A { public: static std::shared_ptr create() { return std::make_shared(); } protected: friend class std::shared_ptr_access; A() {} A(const A &) = delete; const A &operator =(const A &) = delete; }; 

    Wenn dies auch für Sie eine wichtige Ergänzung des Standards ist, können Sie Ihre 2 Cent in die verknüpfte isocpp Google-Gruppe hinzufügen.

    Die Wurzel des Problems ist, dass, wenn die function oder die class, die dein Freund benutzt, Anrufe auf niedriger Ebene an deinen Konstruktor richtet, müssen sie auch mit Freunden verbunden werden. std :: make_shared ist nicht die function, die Ihren Konstruktor aufruft, so dass es keinen Unterschied macht.

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

    std :: _ Ref_count_obj ruft eigentlich deinen Konstruktor auf, also muss es ein Freund sein. Da das ein bisschen unklar ist, benutze ich ein Makro

     #define SHARED_PTR_DECL(T) \ class T; \ typedef std::shared_ptr ##T##Ptr; #define FRIEND_STD_MAKE_SHARED \ template \ friend class std::_Ref_count_obj; 

    Dann sieht Ihre classndeklaration ziemlich einfach aus. Sie können ein einzelnes Makro erstellen, um das ptr und die class zu deklarieren, wenn Sie dies bevorzugen.

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

    Das ist eigentlich ein wichtiges Thema. Um wartbaren, portablen Code zu erstellen, müssen Sie so viel wie möglich von der Implementierung verstecken.

     typedef std::shared_ptr APtr; 

    Versteckt, wie Sie Ihren Smart Pointer ein wenig handhaben, müssen Sie sicher sein, Ihre Typedef zu verwenden. Wenn Sie jedoch immer make_shared erstellen müssen, wird der Zweck abgelehnt.

    Das obige Beispiel erzwingt Code, der Ihre class verwendet, um Ihren Smart-Pointer-Konstruktor zu verwenden, was bedeutet, dass Sie, wenn Sie zu einem neuen Smart-Pointer wechseln, Ihre classndeklaration ändern und eine gute Chance haben, fertig zu werden. Gehen Sie nicht davon aus, dass Ihr nächster Boss oder Projekt den STL-, Boost- oder anderen Plan verwenden wird, um es irgendwann zu ändern.

    Ich habe dies seit fast 30 Jahren getan, und ich habe einen hohen Preis für Zeit, Schmerz und Nebenwirkungen bezahlt, um das zu reparieren, wenn es vor Jahren falsch gemacht wurde.

    Sie können dies verwenden:

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