¿Cómo llamo :: std :: make_shared en una clase con solo constructores protegidos o privados?

187

Tengo este código que no funciona, pero creo que la intención es clara:

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

Pero recibo este error cuando 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

Este mensaje básicamente dice que algún método aleatorio en la pila de ::std::make_sharedcreación de instancias de plantilla no puede acceder al constructor porque está protegido.

Pero realmente quiero usar ambos ::std::make_sharedy evitar que alguien haga un objeto de esta clase que no sea señalado por a ::std::shared_ptr. ¿Hay alguna forma de lograr esto?

De todo género
fuente
Puede marcar la función en el fondo que necesita al constructor como amigo, pero que no será portátil.
Dani
@ Dani: Sí, sería bueno tener una solución portátil. Pero eso funcionaría.
Omnifarious

Respuestas:

109

Esta respuesta es probablemente mejor, y la que probablemente acepte. Pero también se me ocurrió un método que es más feo, pero aún permite que todo siga en línea y no requiere una clase derivada:

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

Editar 2017-01-06: Cambié esto para dejar en claro que esta idea es clara y simplemente extensible a los constructores que toman argumentos porque otras personas estaban proporcionando respuestas en ese sentido y parecían confundidos al respecto.

De todo género
fuente
14
En realidad, soy un gran admirador de esas estructuras sin sentido utilizadas solo como claves . Prefiero esto a la solución de Luc, pero ese podría ser mi biais contra la herencia.
Matthieu M.
2
De acuerdo, también me gusta más.
ildjarn
3
@Berkus: Entonces hazlo en protectedlugar de private. Y por "eso", me estoy refiriendo a la this_is_privateclase, que tal vez debería cambiarse de nombre en tal caso. Usualmente lo llamo constructor_accessen mi código.
dalle
1
Lamentablemente, esto no funciona si su constructor toma parámetros reales; en este caso, simplemente puede pasar {}la etiqueta privada sin tener acceso al nombre del tipo (probado con g ++ 4.9.0). Sin parámetros reales, intenta construir a Apartir de {}, aunque no tengo idea de por qué, y falla. Creo que hacer que el constructor this_is_private sea privado y proporcionar un método estático para crearlo lo corrige, ya que no debería haber forma de acceder a este método desde el exterior a menos que se filtre el tipo en una firma de función miembro.
Stefan
3
Stefan, si le das this_is_privateun ctor privado, puedes hacer de la clase A un amigo. Parece cerrar la escapatoria.
Steven Kramer
78

Mirando los requisitos para std::make_shareden 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], párrafo 1:

Requiere: La expresión ::new (pv) T(std::forward<Args>(args)...), donde pvtiene tipo void*y puntos de almacenamiento adecuados para contener un objeto de tipo T, debe estar bien formada. Aserá un asignador (17.6.3.5). El constructor de copia y el destructor de Ano lanzarán excepciones.

Dado que el requisito se especifica incondicionalmente en términos de esa expresión y cosas como el alcance no se tienen en cuenta, creo que los trucos como la amistad son correctos.

Una solución simple es derivar de A. Esto no necesita hacer Auna interfaz o incluso un tipo polimórfico.

// 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
fuente
1
Oh, esa es una respuesta muy inteligente, y posiblemente mejor que otra en la que había pensado.
Omnifarioso
Sin embargo, una pregunta es: ¿shared_ptr no eliminará una A y no una concrete_A, y esto no podría causar problemas?
Omnifarioso
8
Ahh, es porque shared_ptralmacena un borrador en el momento de la creación de instancias, y si está usando make_sharedel borrador absolutamente tiene que estar usando el tipo correcto.
Omnifarioso
1
@LucDanton La pregunta no se trata de interfaces, ya que el título sugiere que también está pidiendo un ctor privado. Además, por eso estoy en esta pregunta de todos modos. Algún código antiguo que tiene clases de maquiavelo, que tiene un ctor privado y un método de creación que devuelve un puntero sin formato, y estoy tratando de convertirlos en punteros inteligentes.
zahir
2
Me gusta este enfoque (usarlo yo mismo) pero necesitas un destructor virtual. Se extiende bien a los constructores con argumentos (solo proporcione un constructor de paso). Y si está utilizando protegido en lugar de privado , puede hacerlo completamente invisible para los usuarios del encabezado.
Joe Steele
69

Posiblemente la solución más simple. Basado en la respuesta anterior de Mohit Aron e incorporando la sugerencia de 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
fuente
55
Si Atiene constructores no predeterminados que también tendrá que exponerlos: struct make_shared_enabler : public A { template <typename... Args> make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...) {} };. Esto hace que todos los constructores privados sean Avisibles como make_shared_enablerconstructores. Usar la característica de herencia de constructores ( using A::A;) parece que no ayuda aquí porque los constructores seguirán siendo privados.
anton_rh
2
@anton_rh: no puede agregar argumentos de plantilla a las clases internas. Ver aquí .
bobbel
3
Hm ... Parece que tienes razón. En mi caso struct no era local, pero era una estructura privada: class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }. Ver aquí cpp.sh/65qbr .
anton_rh
Esto funciona muy bien. ¿Hay alguna posibilidad de hacer de esto una propiedad heredable, por lo que este patrón no tiene que repetirse varias veces? En particular, la versión que expone constructores no predeterminados sería muy interesante para mí. La versión predeterminada "simplemente" requeriría una construcción sintáctica que reemplace A con cualquier clase que herede la clase. No estoy al tanto de algo así, pero no me sorprendería saber que existe ...
Kjeld Schmidt
30

Aquí hay una buena solución para esto:

#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
fuente
3
Me gusta esto. Se puede simplificar un poco definiendo MakeSharedEnablerlocalmente dentro A::Create().
dlf
Impresionante idea Mohit me ayudó mucho.
Jnana
12

¿Qué tal esto?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
Sean
fuente
13
Eso funciona muy bien. Pero ::std::make_sharedtiene una funcionalidad más allá de simplemente hacer un shared_ptr a algo. Asigna el recuento de referencia junto con el objeto para que estén ubicados cerca uno del otro. Tengo muchas ganas de usar ::std::make_shared.
Omnifarioso
La asignación eliminada y los operadores de copia lo prohíben
Dani
77
Este es realmente el enfoque más directo, a pesar de que no es realmente lo que la pregunta estaba haciendo. make_shared tiene algunas características agradables y trato de usarlo siempre que sea posible, pero en esta situación parece bastante probable que las ventajas de rendimiento en tiempo de ejecución de make_shared no superen la complejidad del código adicional y la ceremonia realmente necesaria para usarlo. Si realmente necesita el rendimiento de make_shared, enloquezca, pero no pase por alto la simplicidad de solo usar el constructor de shared_ptr.
Kevin
Sin embargo, tenga cuidado con las pérdidas de memoria ... vea esta pregunta 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_;
};
alfa
fuente
Esto es en gran medida lo mismo que la respuesta de Luc Danton, aunque convertirlo en una clase local es un buen toque. Alguna explicación para acompañar el código podría hacer que esta sea una respuesta mucho mejor.
Normalmente, quiero escribir una función tan pequeña en el archivo de encabezado pero no en el archivo cc. En segundo lugar, en la práctica, uso una macro que se parece a #define SharedPtrCreate (T) template <typename ... Arg> .....
alpha
Buena respuesta. Incluso lo puse en una macro llamada como IMPLEMENT_CREATE_SHARED (ClassName)
ivan.ukr
8

Como no me gustaban las respuestas ya proporcionadas, decidí buscar y encontré una solución que no es tan genérica como las respuestas anteriores, pero me gusta más (tm). En retrospectiva, no es mucho mejor que el proporcionado por Omnifarius, pero podría haber otras personas a las que también les guste :)

Esto no fue inventado por mí, pero es la idea de Jonathan Wakely (desarrollador de GCC).

Desafortunadamente, no funciona con todos los compiladores porque se basa en un pequeño cambio en la implementación std :: allocate_shared. Pero este cambio es ahora una actualización propuesta para las bibliotecas estándar, por lo que podría ser compatible con todos los compiladores en el futuro. Funciona en GCC 4.7.

La solicitud de cambio del grupo de trabajo de la biblioteca estándar de C ++ está aquí: http://lwg.github.com/issues/lwg-active.html#2070

El parche GCC con un ejemplo de uso está aquí: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

La solución funciona con la idea de usar std :: allocate_shared (en lugar de std :: make_shared) con un asignador personalizado que se declara amigo de la clase con el constructor privado.

El ejemplo del OP se vería así:

#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 ejemplo más complejo que se basa en la utilidad en la que estoy trabajando. Con esto no pude usar la solución de Luc. Pero el de Omnifarius podría adaptarse. No es que mientras que en el ejemplo anterior todos puedan crear un objeto A usando MyAlloc en este, no hay forma de crear A o B además del método 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;
}
Zsolt Rizsányi
fuente
6

Idealmente, creo que la solución perfecta requeriría adiciones al estándar C ++. Andrew Schepler propone lo siguiente:

(Vaya aquí para todo el hilo)

Podemos tomar prestada una idea de boost :: iterator_core_access. Propongo una nueva clasestd::shared_ptr_access sin miembros públicos o protegidos, y para especificar eso para std :: make_shared (args ...) y std :: alloc_shared (a, args ...), las expresiones :: new (pv) T (forward (args) ...) y ptr-> ~ T () deben estar bien formados en el contexto de std :: shared_ptr_access.

Una implementación de std :: shared_ptr_access podría verse así:

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

Si / cuando lo anterior se agrega al estándar, simplemente haríamos:

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

Si esto también le parece una adición importante al estándar, no dude en agregar sus 2 centavos al grupo de Google isocpp vinculado.

Boris Dalstein
fuente
1
Creo que es una buena adición al estándar, pero no es lo suficientemente importante como para tomarme el tiempo de unirme al Grupo de Google y comentar y luego prestar atención a ese grupo y al comentario. :-)
Omnifarious
4

Me doy cuenta de que este hilo es bastante antiguo, pero encontré una respuesta que no requiere herencia o argumentos adicionales para el constructor que no pude ver en ningún otro lado. Sin embargo, no es portátil:

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

He probado en Windows y Linux, es posible que necesite ajustes para diferentes plataformas.

ashleysmithgpu
fuente
1
Estoy tentado a -1 por falta de portabilidad. Las otras respuestas (particularmente las respuestas de 'clase clave') son bastante elegantes y la respuesta no portátil es muy fea. No puedo pensar en una razón por la que usarías la respuesta no portátil. No es más rápido ni nada por el estilo.
Omnifarious
@Omnifarious De hecho, no es portátil y no lo recomendaría, pero creo que esta es la solución semánticamente más correcta. En mi respuesta , hago un enlace a una propuesta de agregar std::shared_ptr_accessal estándar, que podría considerarse que permite hacer lo anterior de una manera simple y portátil.
Boris Dalstein
3

Hay un problema más difícil e interesante que ocurre cuando tienes dos clases A y B estrictamente relacionadas que funcionan juntas.

Digamos que A es la "clase maestra" y B su "esclavo". Si desea restringir la creación de instancias de B solo a A, haría que el constructor de B sea privado, y el amigo B a A así

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

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

Desafortunadamente, llamar std::make_shared<B>()desde un método Ahará que el compilador se queje de B::B()ser privado.

Mi solución a esto es crear una Passclase ficticia pública (igual que nullptr_t) dentro Bque tenga un constructor privado y sea amigo Ay haga que Bel constructor sea público y se agregue Passa sus argumentos, de esta manera.

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
fuente
3

Si también desea habilitar un constructor que tome argumentos, esto puede ayudar un poco.

#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");
}
manifestación
fuente
3

[Editar] Leí el hilo mencionado anteriormente en una std::shared_ptr_access<>propuesta estandarizada . Dentro hubo una respuesta que señalaba una solución std::allocate_shared<>y un ejemplo de su uso. Lo he adaptado a una plantilla de fábrica a continuación, y lo probé bajo gcc C ++ 11/14/17. También funciona con std::enable_shared_from_this<>, por lo que obviamente sería preferible a mi solución original en esta respuesta. Aquí está...

#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] Encontré una solución usando el constructor de alias de puntero compartido. Permite que tanto el ctor como el dtor sean privados, así como el uso del especificador final.

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

Tenga en cuenta que el enfoque anterior no funciona bien std::enable_shared_from_this<>porque la inicial std::shared_ptr<>es para el contenedor y no el tipo en sí. Podemos abordar esto con una clase equivalente que sea compatible con la fábrica ...

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

Por último, alguien dijo que clang se quejaba de que Factory :: Type era privado cuando se usaba como amigo, así que hazlo público si ese es el caso. Exponerlo no hace daño.

usuario1715587
fuente
3

Tuve el mismo problema, pero ninguna de las respuestas existentes fue realmente satisfactoria, ya que necesito pasar argumentos al constructor protegido. Además, necesito hacer esto para varias clases, cada una tomando diferentes argumentos.

A tal efecto, y basándose en varias de las respuestas existentes que utilizan métodos similares, presento esta pequeña 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 )... );
}
Mateo
fuente
1

La raíz del problema es que si la función o clase que tu amigo hace llamadas de nivel inferior a tu constructor, también tienen que ser amigos. std :: make_shared no es la función que en realidad llama a tu constructor, por lo que hacer amigos no hace ninguna diferencia.

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 en realidad está llamando a su constructor, por lo que debe ser un amigo. Como eso es un poco oscuro, 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;

Entonces su declaración de clase parece bastante simple. Puede hacer una sola macro para declarar el ptr y la clase si lo prefiere.

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

Esto es realmente un tema importante. Para que el código portátil se pueda mantener, debe ocultar la mayor cantidad de implementación posible.

typedef std::shared_ptr<A> APtr;

oculta un poco cómo maneja su puntero inteligente, debe asegurarse de usar su typedef. Pero si siempre tienes que crear uno usando make_shared, se pierde el propósito.

El ejemplo anterior obliga al código a usar su clase para usar su constructor de puntero inteligente, lo que significa que si cambia a un nuevo tipo de puntero inteligente, cambia su declaración de clase y tiene una buena posibilidad de terminar. NO asuma que su próximo jefe o proyecto usará el plan stl, boost, etc. para cambiarlo algún día.

Al hacer esto por casi 30 años, he pagado un gran precio en tiempo, dolor y efectos secundarios para reparar esto cuando se hizo mal hace años.

brtip
fuente
2
std::_Ref_count_objEs un detalle de implementación. Eso significa que si bien esta solución podría funcionar para usted, por ahora, en su plataforma. Pero podría no funcionar para otros y dejar de funcionar en cualquier momento que el compilador se actualice o tal vez incluso si solo cambia los indicadores de compilación.
François Andrieux
-3

Puedes usar esto:

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() {};
};
mover el esqueleto
fuente
1
No 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;
}
spise
fuente
Esto es solo un duplicado de esta respuesta: stackoverflow.com/a/27832765/167958
Omnifarious