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

185

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

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();
}

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<_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

Diese Nachricht besagt im Grunde, dass eine zufällige Methode weit unten im Vorlageninstanziierungsstapel von ::std::make_sharednicht auf den Konstruktor zugreifen kann, weil er geschützt ist.

Aber ich möchte wirklich beides verwenden ::std::make_sharedund verhindern, dass jemand ein Objekt dieser Klasse erstellt, auf das a nicht zeigt ::std::shared_ptr. Gibt es eine Möglichkeit, dies zu erreichen?

Allgegenwärtig
quelle
Sie können die Funktion tief unten markieren, die den Konstruktor als Freund benötigt, die jedoch nicht portierbar ist.
Dani
@Dani: Ja, es wäre schön, eine tragbare Lösung zu haben. Aber das würde funktionieren.
Omnifarious

Antworten:

108

Diese Antwort ist wahrscheinlich besser und die, die ich wahrscheinlich akzeptieren werde. Aber ich habe mir auch eine Methode ausgedacht, die hässlicher ist, aber trotzdem alles inline lässt und keine abgeleitete Klasse erfordert:

#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);
}

Edit 2017-01-06: Ich habe dies geändert, um deutlich zu machen, dass diese Idee klar und einfach auf Konstruktoren erweiterbar ist, die Argumente annehmen, weil andere Leute Antworten in diese Richtung gaben und diesbezüglich verwirrt zu sein schienen.

Allgegenwärtig
quelle
14
Eigentlich bin ich ein großer Fan dieser bedeutungslosen Strukturen, die nur als Schlüssel verwendet werden . Ich ziehe dies Lucs Lösung vor, aber das könnte mein Biais gegen die Vererbung sein.
Matthieu M.
2
Einverstanden, das gefällt mir auch besser.
ildjarn
3
@Berkus: Dann mach es protectedstatt private. Und mit "es" beziehe ich mich auf die this_is_privateKlasse, die in einem solchen Fall vielleicht umbenannt werden sollte. Normalerweise nenne ich es constructor_accessin meinem Code.
dalle
1
Leider funktioniert dies nicht, wenn Ihr Konstruktor echte Parameter verwendet. In diesem Fall können Sie einfach {}das private Tag übergeben, ohne Zugriff auf den Typnamen zu haben (getestet mit g ++ 4.9.0). Ohne echte Parameter versucht es, Aaus {} zu konstruieren , obwohl ich keine Ahnung habe, warum und schlägt fehl. Ich denke, den Konstruktor this_is_private privat zu machen und eine statische Methode zum Erstellen bereitzustellen, behebt ihn, da es keine Möglichkeit geben sollte, von außen auf diese Methode zuzugreifen, es sei denn, Sie verlieren den Typ in einer Signatur einer Mitgliedsfunktion.
Stefan
3
Stefan, wenn Sie geben this_is_privateeinen privaten Ctor können Sie Klasse A ein Freund machen. Scheint die Lücke zu schließen.
Steven Kramer
77

Betrachten der Anforderungen für die std::make_sharedErstellung von shared_ptr in 20.7.2.2.6 [util.smartptr.shared.create], Absatz 1:

Erforderlich: Der Ausdruck ::new (pv) T(std::forward<Args>(args)...), bei dem pvTyp void*und Speicherort für ein Objekt vom Typ geeignet sind T, muss gut geformt sein. Amuss ein Zuteiler sein (17.6.3.5). Der Kopierkonstruktor und Destruktor von Adarf keine Ausnahmen auslösen.

Da die Anforderung in Bezug auf diesen Ausdruck unbedingt festgelegt ist und Dinge wie der Umfang nicht berücksichtigt werden, denke ich, dass Tricks wie Freundschaft richtig sind.

Eine einfache Lösung besteht darin, daraus abzuleiten A. Dies muss keine ASchnittstelle oder sogar einen polymorphen Typ erfordern .

// 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>();
}
Luc Danton
quelle
1
Oh, das ist eine sehr kluge Antwort und möglicherweise besser als eine andere, an die ich gedacht hatte.
Omnifarious
Eine Frage ist jedoch, ob der shared_ptr nicht ein A und keinen konkreten_A löscht und dies nicht zu Problemen führen kann.
Omnifarious
8
Ahh, das liegt daran, dass shared_ptrzum Zeitpunkt der Instanziierung ein Deleter gespeichert wird und wenn Sie make_sharedden Deleter verwenden, muss unbedingt der richtige Typ verwendet werden.
Omnifarious
1
@ LucDanton Bei der Frage geht es nicht um Schnittstellen, da der Titel besagt, dass er auch nach einem privaten Ctor fragt. Deshalb bin ich auch bei dieser Frage. Einige alte Codes mit machiavelli-Klassen, die einen privaten ctor und eine create-Methode haben, die einen rohen Zeiger zurückgibt, und ich versuche, sie in intelligente Zeiger zu konvertieren.
Zahir
2
Ich mag diesen Ansatz (ich benutze ihn selbst), aber Sie brauchen einen virtuellen Destruktor. Es erstreckt sich gut auf Konstruktoren mit Argumenten (stellen Sie einfach einen Passthrough-Konstruktor bereit). Wenn Sie eher geschützte als private verwenden , können Sie diese für Benutzer des Headers vollständig unsichtbar machen.
Joe Steele
69

Möglicherweise die einfachste Lösung. Basierend auf der vorherigen Antwort von Mohit Aron und unter Einbeziehung des Vorschlags von 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() {}  
};
Mark Tolley
quelle
5
Wenn Aes nicht standardmäßige Konstruktoren gibt, müssen Sie diese auch verfügbar machen : struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Dies macht alle privaten Konstruktoren von Aals make_shared_enablerKonstruktoren sichtbar . Die Verwendung der Konstruktor-Vererbungsfunktion ( using A::A;) scheint hier nicht zu helfen, da Konstruktoren weiterhin privat sind.
anton_rh
2
@anton_rh: Sie können inneren Klassen keine Vorlagenargumente hinzufügen. Siehe hier .
Bobbel
3
Hm ... Scheint, du hast recht. In meinem Fall war struct nicht lokal, sondern eine private struct : class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Siehe hier cpp.sh/65qbr .
anton_rh
Das funktioniert super. Gibt es eine Möglichkeit, dies zu einer vererbbaren Eigenschaft zu machen, sodass dieses Muster nicht mehrmals wiederholt werden muss? Besonders die Version, die nicht standardmäßige Konstruktoren verfügbar macht, wäre für mich sehr interessant. Die Standardversion würde "lediglich" ein syntaktisches Konstrukt erfordern, das A durch die Klasse ersetzt, die die Klasse erbt. Ich bin mir so etwas nicht bewusst, aber ich wäre nicht überrascht zu erfahren, dass es existiert ...
Kjeld Schmidt
30

Hier ist eine gute Lösung dafür:

#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>();
}
Mohit Aron
quelle
3
Ich mag das. Es kann ein wenig einfacher gemacht werden, indem es MakeSharedEnablerlokal im Inneren definiert wird A::Create().
dlf
Tolle Idee, Mohit, es hat mir sehr geholfen.
Jnana
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_;
};
Alpha
quelle
Dies ist weitgehend das Gleiche wie die Antwort von Luc Danton, obwohl es eine nette Geste ist, daraus eine lokale Klasse zu machen. Eine Erklärung zum Code könnte dies zu einer viel besseren Antwort machen.
Normalerweise möchte ich eine so kleine Funktion in die Header-Datei schreiben, aber nicht in die CC-Datei. Zweitens verwende ich in der Praxis ein Makro, das aussieht wie #define SharedPtrCreate (T) template <Typname ... Arg> .....
Alpha
Gute Antwort. Ich würde das sogar in ein Makro namens IMPLEMENT_CREATE_SHARED (ClassName)
einfügen
11

Wie wäre es damit?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Sean
quelle
13
Das funktioniert super. Hat ::std::make_sharedaber Funktionen, die über das einfache Erstellen eines shared_ptr für etwas hinausgehen. Es ordnet den Referenzzähler zusammen mit dem Objekt so zu, dass sie nahe beieinander liegen. Ich möchte wirklich, wirklich verwenden ::std::make_shared.
Omnifarious
Die gelöschten Zuweisungs- und Kopieroperatoren verbieten dies
Dani
7
Dies ist wirklich der einfachste Ansatz, obwohl es nicht wirklich das ist, was die Frage gestellt hat. make_shared hat einige nette Eigenschaften und ich versuche es wo immer möglich zu verwenden, aber in dieser Situation ist es sehr wahrscheinlich, dass die Laufzeitleistungsvorteile von make_shared die zusätzliche Codekomplexität und -zeremonie, die tatsächlich für die Verwendung erforderlich ist, nicht überwiegen. Wenn Sie die Leistung von make_shared wirklich brauchen, werden Sie verrückt, aber übersehen Sie nicht die Einfachheit, nur den Konstruktor von shared_ptr zu verwenden.
Kevin
Seien Sie jedoch vorsichtig bei Speicherverlusten ... siehe diese Frage stackoverflow.com/a/14837300/2149539
dgmz
8

Da mir die bereits bereitgestellten Antworten nicht gefallen haben, habe ich mich entschlossen, nach einer Lösung zu suchen, die nicht so allgemein ist wie die vorherigen Antworten, aber mir gefällt sie besser (tm). Rückblickend 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. Diese Änderung ist jetzt ein vorgeschlagenes Update für die Standardbibliotheken, sodass sie möglicherweise in Zukunft von allen Compilern unterstützt wird. Es funktioniert mit GCC 4.7.

Die Änderungsanforderung für die C ++ - Standardbibliotheksarbeitsgruppe ist 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 Allokator zu verwenden, der mit dem privaten Konstruktor als Freund der Klasse deklariert wird.

Das Beispiel aus dem OP würde folgendermaßen aussehen:

#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;
}

Ein komplexeres Beispiel, das auf dem Dienstprogramm basiert, an dem ich arbeite. Damit konnte ich Lucs Lösung nicht gebrauchen. Aber der von Omnifarius könnte angepasst werden. Nicht, dass im vorherigen Beispiel jeder mit dem MyAlloc ein A-Objekt erstellen kann. In diesem Fall gibt es außer der create () -Methode keine Möglichkeit, A oder B zu erstellen.

#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;
}
Zsolt Rizsányi
quelle
6

Idealerweise würde die perfekte Lösung Ergänzungen zum C ++ - Standard erfordern. Andrew Schepler schlägt Folgendes vor:

( Hier geht es zum ganzen Thread)

Wir können eine Idee von boost :: iterator_core_access ausleihen. Ich schlage eine neue Klasse vorstd::shared_ptr_access ohne öffentliche oder geschützte Mitglieder vor und spezifiziere 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 <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;
    };
}

Verwendung

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

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;
};

Wenn dies für Sie auch nach einer wichtigen Ergänzung des Standards klingt, können Sie der verknüpften isocpp-Google-Gruppe Ihre 2 Cent hinzufügen.

Boris Dalstein
quelle
1
Ich denke, es ist eine gute Ergänzung zum Standard, aber es ist nicht wichtig genug, dass ich mir die Zeit nehme, der Google-Gruppe beizutreten und zu kommentieren und dann auf diese Gruppe und den Kommentar zu achten. :-)
Omnifarious
4

Mir ist klar, dass dieser Thread ziemlich alt ist, aber ich habe eine Antwort gefunden, die keine Vererbung oder zusätzliche Argumente für den Konstruktor erfordert, die ich anderswo nicht sehen konnte. Es ist jedoch nicht tragbar:

#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());
}

Ich habe unter Windows und Linux getestet, es muss möglicherweise für verschiedene Plattformen angepasst werden.

ashleysmithgpu
quelle
1
Ich bin versucht, es wegen mangelnder Portabilität zu -1. Die anderen Antworten (insbesondere die Antworten der Schlüsselklasse) sind ziemlich elegant und die nicht tragbare Antwort sehr hässlich. Ich kann mir keinen Grund vorstellen, warum Sie die nicht tragbare Antwort verwenden würden. Es ist nicht schneller oder so etwas.
Omnifarious
@ Omnifarious Es ist in der Tat nicht portabel und ich würde es nicht empfehlen, aber ich glaube, dass dies tatsächlich die semantisch korrekteste Lösung ist. In meiner Antwort verweise ich auf einen Vorschlag zur Ergänzung std::shared_ptr_accessdes Standards, der als einfach und tragbar angesehen werden kann.
Boris Dalstein
3

Es gibt ein haarigeres und interessanteres Problem, das auftritt, wenn zwei eng verwandte Klassen A und B zusammenarbeiten.

Sagen wir, A ist die "Meisterklasse" und B ihr "Sklave". Wenn Sie die Instanziierung von B nur auf A beschränken möchten, machen Sie den Konstruktor von B privat und Freund B auf A wie folgt

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

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

Leider bringt das Aufrufen std::make_shared<B>()von einer Methode von Aden Compiler dazu, sich zu beschwerenB::B() zu , privat zu sein.

Meine Lösung hierfür besteht darin, eine öffentliche PassDummy-Klasse (genau wie nullptr_t) im Inneren zu erstellen, die einen Bprivaten Konstruktor hat und mit dem Konstruktor befreundet ist Aund ihn Böffentlich macht und Passseine Argumente wie folgt ergänzt .

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);
  }
};
Keebus
quelle
3

Wenn Sie auch einen Konstruktor aktivieren möchten, der Argumente akzeptiert, kann dies etwas hilfreich sein.

#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");
}
Demo
quelle
3

[Bearbeiten] Ich habe den oben genannten Thread zu einem standardisierten std::shared_ptr_access<>Vorschlag gelesen . Darin befand sich eine Antwort, in der ein Fix std::allocate_shared<>und ein Beispiel für seine Verwendung vermerkt waren. Ich habe es unten an eine Factory-Vorlage angepasst und unter gcc C ++ 11/14/17 getestet. Es funktioniert auch mit std::enable_shared_from_this<>, wäre also meiner ursprünglichen Lösung in dieser Antwort offensichtlich vorzuziehen. Hier ist es...

#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";
}

[Orig] Ich habe eine Lösung mit dem Aliasing-Konstruktor für gemeinsam genutzte Zeiger gefunden. Es ermöglicht sowohl ctor als auch dtor, privat zu sein, sowie die Verwendung des endgültigen Spezifizierers.

#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);
}

Beachten Sie, dass der obige Ansatz nicht gut funktioniert, std::enable_shared_from_this<>da sich die Initiale std::shared_ptr<>auf den Wrapper und nicht auf den Typ selbst bezieht . Wir können dies mit einer äquivalenten Klasse beheben, die mit der Fabrik kompatibel ist ...

#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";
}

Schließlich sagte jemand, Clang habe sich darüber beschwert, dass Factory :: Type als Freund privat sei. Machen Sie es also einfach öffentlich, wenn dies der Fall ist. Das Aussetzen schadet nicht.

user1715587
quelle
3

Ich hatte das gleiche Problem, aber keine der vorhandenen Antworten war wirklich zufriedenstellend, da ich Argumente an den geschützten Konstruktor übergeben muss. Außerdem muss ich dies für mehrere Klassen tun, die jeweils unterschiedliche Argumente verwenden.

Zu diesem Zweck und auf der Grundlage einiger der vorhandenen Antworten, die alle ähnliche Methoden verwenden, präsentiere ich dieses kleine Nugget:

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 )... );
}
Matthew
quelle
1

Die Wurzel des Problems liegt darin, dass die Funktion oder Klasse, die Ihr Freund Ihrem Konstruktor auf niedrigerer Ebene aufruft, ebenfalls befreundet sein muss. std :: make_shared ist nicht die Funktion, die Ihren Konstruktor tatsächlich aufruft, so dass eine Freundschaft keinen Unterschied macht.

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 ruft Ihren Konstruktor tatsächlich auf, daher muss er ein Freund sein. Da das etwas dunkel ist, verwende ich ein Makro

#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;

Dann sieht Ihre Klassendeklaration ziemlich einfach aus. Sie können ein einzelnes Makro zum Deklarieren des ptr und der Klasse erstellen, wenn Sie dies bevorzugen.

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

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

typedef std::shared_ptr<A> APtr;

Wenn Sie nicht wissen, wie Sie mit Ihrem Smart Pointer umgehen, müssen Sie unbedingt Ihr typedef verwenden. Wenn Sie jedoch immer eine mit make_shared erstellen müssen, wird der Zweck zunichte gemacht.

Das obige Beispiel erzwingt, dass Code, der Ihre Klasse verwendet, Ihren Smart Pointer-Konstruktor verwendet. Wenn Sie also zu einer neuen Variante von Smart Pointer wechseln, ändern Sie Ihre Klassendeklaration und haben eine gute Chance, fertig zu werden. Gehen Sie NICHT davon aus, dass Ihr nächster Chef oder Ihr nächstes Projekt STL, Boost usw. verwenden wird, um es eines Tages zu ändern.

Seit fast 30 Jahren habe ich einen hohen Preis für Zeit, Schmerzen und Nebenwirkungen gezahlt, um dies zu reparieren, als es vor Jahren falsch gemacht wurde.

brtip
quelle
2
std::_Ref_count_objist ein Implementierungsdetail. Das bedeutet, dass diese Lösung möglicherweise vorerst auf Ihrer Plattform für Sie funktioniert. Aber es funktioniert möglicherweise nicht für andere und funktioniert möglicherweise nicht mehr, wenn Ihr Compiler aktualisiert wird oder wenn Sie nur die Kompilierungsflags ändern.
François Andrieux
-3

Sie können dies verwenden:

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() {};
};
Boogie
quelle
1
Wird nicht verwendet std::make_shared.
Brian
-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;
}
spise
quelle
Dies ist nur ein Duplikat dieser Antwort: stackoverflow.com/a/27832765/167958
Omnifarious