Come posso chiamare :: std :: make_shared su una classe con solo costruttori protetti o privati?


187

Ho questo codice che non funziona, ma penso che l'intento sia chiaro:

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

Ma ottengo questo errore quando lo compilo:

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

Questo messaggio in sostanza dice che alcuni metodi casuali in fondo allo stack di istanze del modello ::std::make_sharednon possono accedere al costruttore perché è protetto.

Ma voglio davvero usare entrambi ::std::make_sharede impedire a chiunque di creare un oggetto di questa classe che non è indicato da a ::std::shared_ptr. C'è un modo per raggiungere questo obiettivo?


Puoi contrassegnare in profondità la funzione che necessita del costruttore come amico, ma che non sarà portatile.
Dani,

@Dani: Sì, sarebbe bello avere una soluzione portatile. Ma avrebbe funzionato.
Onnipotente il

Risposte:


109

Questa risposta è probabilmente migliore e quella che probabilmente accetterò. Ma ho anche escogitato un metodo più brutto, ma lascia comunque tutto in linea e non richiede una classe derivata:

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

Modifica 06/01/2017: ho cambiato questo per chiarire che questa idea è chiaramente e semplicemente estendibile ai costruttori che accettano argomenti perché altre persone fornivano risposte in tal senso e sembravano confuse al riguardo.


14
In realtà, sono un grande fan di quelle strutture insignificanti usate solo come chiavi . Preferisco questo alla soluzione di Luc, ma potrebbe essere la mia propensione all'eredità.
Matthieu M.

2
D'accordo, mi piace anche questo.
ildjarn

3
@Berkus: poi fallo protectedinvece di private. E con "esso", mi riferisco alla this_is_privateclasse, che forse dovrebbe essere rinominata in tal caso. Di solito lo chiamo constructor_accessnel mio codice.
dalle

1
Purtroppo questo non funziona se il tuo costruttore accetta parametri reali; in questo caso puoi semplicemente passare {}per il tag privato senza avere accesso al nome del tipo (testato con g ++ 4.9.0). Senza parametri reali cerca di costruire Ada {}, anche se non ho idea del perché, e fallisce. Penso che rendere privato il costruttore this_is_private e fornire un metodo statico per crearlo lo risolva, poiché non dovrebbe esserci modo di accedere a questo metodo dall'esterno se non si perde il tipo in una firma della funzione membro.
Stefan,

3
Stefan, se dai this_is_privateun servizio privato puoi fare amicizia con la classe A. Sembra chiudere la scappatoia.
Steven Kramer,

78

Osservando i requisiti per la std::make_sharedcreazione in 20.7.2.2.6 shared_ptr [util.smartptr.shared.create], paragrafo 1:

Richiede: l'espressione ::new (pv) T(std::forward<Args>(args)...), dove pvha tipovoid* e punti di archiviazione adatti a contenere un oggetto di tipo T, deve essere ben formata. Adeve essere un allocatore (17.6.3.5). Il costruttore di copie e il distruttore Anon devono generare eccezioni.

Poiché il requisito è specificato incondizionatamente in termini di tale espressione e cose come l'ambito non vengono prese in considerazione, penso che i trucchi come l'amicizia siano giusti.

Una soluzione semplice è quella di derivare A. Ciò non richiede la creazione di Aun'interfaccia o addirittura di un tipo polimorfico.

// 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
Oh, questa è una risposta molto intelligente, e forse migliore di un'altra a cui avevo pensato.
Onnipotente il

Una domanda però, shared_ptr non cancellerà una A e non una concrete_A, e questo non potrebbe causare problemi?
Onnipotente il

8
Ahh, è perché shared_ptrmemorizza un deleter al momento dell'istanza, e se stai usando make_sharedil deleter devi assolutamente usare il tipo giusto.
Onnipotente il

1
@LucDanton La domanda non riguarda le interfacce, poiché il titolo suggerisce che sta anche chiedendo un gestore privato. Inoltre, ecco perché sono su questa domanda comunque. Qualche vecchio codice con classi machiavelli, che ha un ctor privato e un metodo di creazione che restituisce un puntatore non elaborato, e sto provando a convertirli in puntatori intelligenti.
zahir,

2
Mi piace questo approccio (usandolo da solo) ma hai bisogno di un distruttore virtuale. Si estende bene ai costruttori con argomenti (basta fornire un costruttore passthrough). E se stai usando protetto piuttosto che privato , puoi renderlo completamente invisibile agli utenti dell'intestazione.
Joe Steele,

69

Forse la soluzione più semplice. Basato sulla risposta precedente di Mohit Aron e che incorpora il suggerimento di 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
se Aha i costruttori non predefinite sarà anche necessario per esporre loro: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Ciò rende Avisibili tutti i costruttori privati come make_shared_enablercostruttori. L'uso della funzione di ereditarietà dei costruttori ( using A::A;) sembra non essere utile qui perché i costruttori saranno ancora privati.
anton_rh,

2
@anton_rh: non è possibile aggiungere argomenti template alle classi interne. Vedi qui .
Bob

3
Hm ... Sembra che tu abbia ragione. Nel mio caso struct non era locale, ma era una struttura privata: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Vedi qui cpp.sh/65qbr .
anton_rh,

Funziona benissimo. C'è qualche possibilità di renderlo una proprietà ereditabile, quindi questo schema non deve essere ripetuto più volte? In particolare, la versione che espone costruttori non predefiniti sarebbe molto interessante per me. La versione predefinita richiederebbe "semplicemente" un costrutto sintattico che sostituisce A con qualunque sia la classe che eredita la classe. Non sono a conoscenza di nulla del genere, ma non sarei sorpreso di sapere che esiste ...
Kjeld Schmidt

30

Ecco una soluzione accurata per questo:

#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
Mi piace questo. Può essere reso un po 'più semplice definendo MakeSharedEnablerlocalmente all'interno A::Create().
dlf

Fantastica idea Mohit, mi ha aiutato molto.
Jnana,

12

Cosa ne pensi di questo?

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

13
Funziona benissimo. Ma ::std::make_sharedha funzionalità al di sopra e al di là del semplice fare un shared_ptr a qualcosa. Alloca il conteggio dei riferimenti insieme all'oggetto in modo che si trovino vicini l'uno all'altro. Voglio davvero, davvero usare ::std::make_shared.
Onnipotente il

Gli incaricati della cancellazione e gli operatori delle copie vietati lo vietano
Dani,

7
Questo è davvero l'approccio più diretto, anche se non è proprio quello che la domanda stava ponendo. make_shared ha alcune belle caratteristiche e cerco di usarlo dove possibile, ma in questa situazione sembra abbastanza probabile che i vantaggi di runtime di make_shared non superino la complessità del codice extra e la cerimonia effettivamente richiesta per usarlo. Se hai davvero bisogno delle prestazioni di make_shared, impazzisci, ma non trascurare la semplicità di usare semplicemente il costruttore di shared_ptr.
Kevin,

Fate attenzione a perdite di memoria se ... vedere questa domanda 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_;
};

Questa è in gran parte la stessa cosa della risposta di Luc Danton, sebbene trasformarla in una classe locale sia un bel tocco. Alcune spiegazioni per accompagnare il codice potrebbero rendere questa una risposta molto migliore.

Normalmente, voglio scrivere una funzione così piccola nel file header ma non nel file cc. In secondo luogo, in pratica, utilizzo una macro che assomiglia a #define SharedPtrCreate (T) template <typename ... Arg> .....
alpha

Buona risposta. Lo metterei anche in una macro chiamata come IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr

8

Dato che non mi piacevano le risposte già fornite, ho deciso di cercare e ho trovato una soluzione non generica come le risposte precedenti, ma mi piace di più (tm). In retrospettiva non è molto più bello di quello fornito da Omnifarius ma potrebbero esserci anche altre persone a cui piace :)

Questo non è stato inventato da me, ma è l'idea di Jonathan Wakely (sviluppatore GCC).

Sfortunatamente non funziona con tutti i compilatori perché si basa su una piccola modifica nell'implementazione std :: allocate_shared. Ma questa modifica è ora un aggiornamento proposto per le librerie standard, quindi potrebbe essere supportato da tutti i compilatori in futuro. Funziona su GCC 4.7.

La richiesta di modifica del gruppo di lavoro della libreria standard C ++ è qui: http://lwg.github.com/issues/lwg-active.html#2070

La patch GCC con un esempio di utilizzo è qui: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

La soluzione funziona sull'idea di utilizzare std :: allocate_shared (anziché std :: make_shared) con un allocatore personalizzato che viene dichiarato amico della classe con il costruttore privato.

L'esempio dell'OP sarebbe simile al seguente:

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

Un esempio più complesso basato sull'utilità su cui sto lavorando. Con questo non ho potuto usare la soluzione di Luc. Ma quello di Omnifarius potrebbe essere adattato. Non che mentre nell'esempio precedente tutti possano creare un oggetto A usando MyAlloc in questo non c'è modo di creare A o B oltre al metodo create ().

#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

Idealmente, penso che la soluzione perfetta richiederebbe aggiunte allo standard C ++. Andrew Schepler propone quanto segue:

(Vai qui per l'intero thread)

possiamo prendere in prestito un'idea da boost :: iterator_core_access. Propongo una nuova classestd::shared_ptr_access senza membri pubblici o protetti e per specificare che per std :: make_shared (args ...) e std :: alloc_shared (a, args ...), le espressioni :: new (pv) T (forward (args) ...) e ptr-> ~ T () devono essere ben formati nel contesto di std :: shared_ptr_access.

Un'implementazione di std :: shared_ptr_access potrebbe apparire come:

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

uso

Se / quando quanto sopra viene aggiunto allo standard, faremmo semplicemente:

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

Se anche questo sembra un'importante aggiunta allo standard, sentiti libero di aggiungere i tuoi 2 centesimi al gruppo Google isocpp collegato.


1
Penso che sia una buona aggiunta allo standard, ma non è abbastanza importante per me prendere il tempo di unirmi al gruppo Google e commentare, quindi prestare attenzione a quel gruppo e al commento. :-)
Onnipotente l'

4

Mi rendo conto che questo thread è piuttosto vecchio, ma ho trovato una risposta che non richiede ereditarietà o argomenti extra per il costruttore che non ho potuto vedere altrove. Non è portatile però:

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

Ho testato su Windows e Linux, potrebbe essere necessario apportare modifiche per piattaforme diverse.


1
Sono tentato di -1 per mancanza di portabilità. Le altre risposte (in particolare le risposte della "classe chiave") sono piuttosto eleganti e la risposta non portatile molto brutta. Non riesco a pensare a un motivo per cui useresti la risposta non portatile. Non è più veloce o niente del genere.
Onnipotente il

@Omnifarious È davvero non portatile e non lo consiglierei, ma credo che questa sia in realtà la soluzione semanticamente più corretta. Nella mia risposta , mi collego a una proposta di aggiunta std::shared_ptr_accessallo standard, che potrebbe essere vista come consentire di fare quanto sopra in modo semplice e portatile.
Boris Dalstein,

3

C'è un problema più peloso e interessante che si verifica quando hai due classi A e B strettamente correlate che lavorano insieme.

Dì che A è la "classe principale" e B il suo "schiavo". Se vuoi limitare l'istanza di B solo ad A, rendi privato il costruttore di B e l'amico B ad A in questo modo

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

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

Purtroppo chiamare std::make_shared<B>()da un metodo Afarà lamentare il compilatore di B::B()essere privato.

La mia soluzione a questo è quella di creare una Passclasse fittizia pubblica (proprio come nullptr_t) all'interno Bche abbia un costruttore privato ed è amico Ae rende Bpubblico il costruttore e aggiunga Passai suoi argomenti, come questo.

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

Se vuoi anche abilitare un constuctor che accetta argomenti, questo potrebbe aiutare un po '.

#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

[Modifica] Ho letto il thread sopra indicato in una std::shared_ptr_access<>proposta standardizzata . All'interno c'era una risposta notando una correzione std::allocate_shared<>e un esempio del suo utilizzo. L'ho adattato a un modello di fabbrica qui sotto e l'ho testato sotto gcc C ++ 11/14/17. Funziona anche con std::enable_shared_from_this<>, quindi sarebbe ovviamente preferibile alla mia soluzione originale in questa risposta. Ecco qui...

#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] Ho trovato una soluzione usando il costruttore di aliasing puntatore condiviso. Permette sia al ctor che al dtor di essere privati, oltre all'uso dell'identificatore finale.

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

Si noti che l'approccio sopra non funziona bene std::enable_shared_from_this<> perché l'iniziale std::shared_ptr<>è il wrapper e non il tipo stesso. Possiamo affrontarlo con una classe equivalente compatibile con la fabbrica ...

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

Infine, qualcuno ha detto che Clang si è lamentato del fatto che Factory :: Type fosse privato quando usato come amico, quindi rendilo pubblico in questo caso. Esporlo non fa male.


3

Ho avuto lo stesso problema, ma nessuna delle risposte esistenti è stata davvero soddisfacente in quanto ho bisogno di passare argomenti al costruttore protetto. Inoltre, devo farlo per diverse classi, ognuna prendendo argomenti diversi.

A tal fine, e basandomi su alcune delle risposte esistenti che utilizzano tutti metodi simili, presento questo piccolo pepita:

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

La radice del problema è che se la funzione o la classe che ami effettua chiamate di livello inferiore al tuo costruttore, anche loro devono essere amici. std :: make_shared non è la funzione che in realtà chiama il tuo costruttore in modo così amichevole che non fa differenza.

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 in realtà chiama il tuo costruttore, quindi deve essere un amico. Dal momento che è un po 'oscuro, io uso una macro

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

Quindi la tua dichiarazione di classe sembra abbastanza semplice. Puoi creare una singola macro per dichiarare il ptr e la classe se preferisci.

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

Questo è in realtà un problema importante. Per rendere il codice gestibile e portatile è necessario nascondere il più possibile dell'implementazione.

typedef std::shared_ptr<A> APtr;

nasconde un po 'come stai gestendo il tuo puntatore intelligente, devi essere sicuro di usare il tuo typedef. Ma se devi sempre crearne uno usando make_shared, questo vanifica lo scopo.

L'esempio precedente impone al codice di utilizzare la classe per utilizzare il costruttore di puntatori intelligenti, il che significa che se si passa a un nuovo tipo di puntatore intelligente, si modifica la dichiarazione di classe e si hanno buone possibilità di finire. NON dare per scontato che il tuo prossimo capo o progetto userà il piano stl, boost ecc. Per cambiarlo un giorno.

In questo modo da quasi 30 anni, ho pagato un grande prezzo in tempo, dolore ed effetti collaterali per ripararlo quando è stato fatto male anni fa.


2
std::_Ref_count_objè un dettaglio di implementazione. Ciò significa che mentre questa soluzione potrebbe funzionare per te, per ora, sulla tua piattaforma. Ma potrebbe non funzionare per gli altri e potrebbe smettere di funzionare ogni volta che si aggiornano i compilatori o forse anche se si cambiano solo i flag di compilazione.
François Andrieux, il

-3

Puoi usare questo:

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

Questo è solo un duplicato di questa risposta: stackoverflow.com/a/27832765/167958
omnifarious
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.