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


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 obtengo 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 instanciación de plantilla de ::std::make_shared no puede acceder al constructor porque está protegido.

Pero realmente quiero usar ambos ::std::make_shared y evitar que alguien haga un objeto de esta clase que no esté apuntado por un ::std::shared_ptr. ¿Hay alguna manera de lograr esto?

Author: Omnifarious, 2011-11-16

14 answers

Esta respuesta es probablemente mejor, y la que probablemente aceptaré. Pero también se me ocurrió un método que es más feo, pero todavía deja 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 esas líneas y parecían confundidas sobre esto.

 93
Author: Omnifarious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:40

Mirando los requisitos para std::make_shared en 20.7.2.2.6 shared_ptr creation [util.smartptr.compartir.crear], párrafo 1:

Requiere: La expresión ::new (pv) T(std::forward<Args>(args)...), donde pv tiene tipo void* y apunta al almacenamiento adecuado para sostener un objeto de tipo T, debe estar bien formada. A será un asignador (17.6.3.5). El constructor de copia y el destructor de A no 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 trucos como la amistad están a la derecha.

Una solución simple es derivar de A. Esto no necesita hacer A una 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>();
}
 59
Author: Luc Danton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-08 07:04:42

Posiblemente la solución más simple. Basado en la anterior respuesta 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() {}  
};
 53
Author: Mark Tolley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:40

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>();
}
 19
Author: Mohit Aron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-23 14:21:26
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_;
};
 9
Author: alpha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-08 06:24:17

¿Qué tal esto?

static std::shared_ptr<A> create()
{
    std::shared_ptr<A> pA(new A());
    return pA;
}
 8
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-16 05:21:52

Como no me gustaron 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 más agradable que el proporcionado por Omnifarius, pero podría haber otras personas a las que también les guste:)

Esto no es 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 std::implementación 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 uso de ejemplo está aquí: http://old.nabble.com/Re%3A--v3--Implement-pointer_traits-and-allocator_traits-p31723738.html

El 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 podía usar la solución de Luc. Pero el de Omnifarius podría ser adaptado. No es que mientras que en el ejemplo anterior todo el mundo puede crear un objeto A usando el MyAlloc en esta no hay manera 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;
}
 8
Author: Zsolt Rizsányi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-05 12:34:30

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 otro lugar. 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, puede necesitar ajustes para diferentes plataformas.

 3
Author: ashleysmithgpu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-10 12:39:28

Si también desea habilitar un constuctor que toma 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");
}
 2
Author: demo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-05 12:22:27

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

(Ir aquí para todo el hilo)

Podemos tomar prestada una idea de boost::iterator_core_access. Propongo una nueva clase std::shared_ptr_access sin público o miembros protegidos, y especificar que para std:: make_shared (args...) y std:: alloc_shared(a, args...), el expresiones:: new (pv) T(forward(args)...) y ptr->~T () debe ser bien formado en el contexto de std::shared_ptr_access.

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

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 se agrega lo anterior 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 suena como una adición importante al estándar para usted, no dude en agregar sus 2 centavos al Grupo de Google isocpp vinculado.

 2
Author: Boris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-10 13:14:50

Hay un problema más peludo e interesante que sucede cuando tienes dos clases estrictamente relacionadas A y B que trabajan juntas.

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

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

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

Desafortunadamente llamar a std::make_shared<B>() desde un método de A hará que el compilador se queje de que B::B() es privado.

Mi solución a esto es crear un public Pass clase ficticia (al igual que nullptr_t) dentro de B que tiene constructor privado y es amigo de A y hace público el constructor de B y agrega Pass a sus argumentos, así.

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);
  }
};
 1
Author: keebus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-26 23:24:01

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 realmente llama a su constructor, por lo que 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 necesita ser un amigo. Dado que 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. Usted puede hacer un solo 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()
    {}
};

Este es en realidad un tema importante. Para hacer que el código portátil sea mantenible, debe ocultar la mayor parte de la implementación posible.

typedef std::shared_ptr<A> APtr;

Oculta un poco cómo está manejando su puntero inteligente, debe asegurarse de usar su typedef. Pero si siempre tienes que crear uno usando make_shared, frustra 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 cambias a un nuevo tipo de puntero inteligente, cambias tu declaración de clase y tienes una oportunidad decente de terminar. NO asuma que su próximo jefe o proyecto utilizará stl, boost, etc. planea cambiarlo algún día.

Haciendo esto durante 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.

 0
Author: brtip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-31 18:23:26

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() {};
};
 -3
Author: boogie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-09 11:31:11
#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;
}
 -3
Author: spise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-07 08:18:10