c++ - smart - ¿Cómo invoco:: std:: make_shared en una clase con solo constructores protegidos o privados?
smart pointers c++ (13)
¿Qué tal esto?
static std::shared_ptr<A> create()
{
std::shared_ptr<A> pA(new A());
return pA;
}
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 ::std::make_shared
instancias de la plantilla desde ::std::make_shared
no puede acceder al constructor porque está protegido.
Pero realmente quiero usar both ::std::make_shared
e impedir que alguien haga un objeto de esta clase que no sea apuntado por a ::std::shared_ptr
. ¿Hay alguna forma de lograr esto?
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>();
}
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 mejor que el proporcionado por Omnifarius, pero podría haber otras personas a las que les guste también :)
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 de std :: allocate_shared. Pero este cambio ahora es una actualización propuesta para las bibliotecas estándar, por lo que podría ser respaldado por 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 de GCC con un uso de ejemplo 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 sea declarado amigo de la clase con el constructor privado.
El ejemplo del PO serí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 podría usar la solución de Luc. Pero el de Omnifarius podría ser adaptado. No es que mientras en el ejemplo anterior todo el mundo puede crear un objeto A usando el MyAlloc, en este caso 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;
}
Hay un problema más peludo e interesante que ocurre cuando tienes dos clases estrictamente relacionadas, A y B, que funcionan juntas.
Say A es la "clase maestra" y B es "esclavo". Si quieres restringir la creación de instancias de B solo a A, harías que el constructor de B sea privado, y amigo B a A como este
class B
{
public:
// B your methods...
private:
B();
friend class A;
};
Lamentablemente, al 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 una clase ficticia Pass
pública (como nullptr_t
) dentro de B
que tenga un constructor privado y sea amigo de A
y haga que el constructor de B
público y agregue Pass
a sus argumentos, como este.
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);
}
};
He creado este tipo de plantilla que supera muy bien el problema de los constructores protegidos:
// helper template not used directly in code:
template<typename T>
struct CHelpExposeProtectedConstructor : public T
{
template<typename...Args>
CHelpExposeProtectedConstructor(Args...args)
: T(std::forward<Args>(args)...)
{}
};
/**
* this uses std::make_shared on classes which have protected constructors
*/
template<typename T, typename...Args>
auto MakeSharedFromProtected(Args...args) -> std::shared_ptr<T>
{
return std::make_shared<CHelpExposeProtectedConstructor<T>>(std::forward<Args>(args)...);
}
Simplemente utiliza MakeSharedFromProtected
lugar de make_shared
y problema resuelto.
Lamentablemente, esto no ayuda en el caso de constructores privados.
Me doy cuenta de que este hilo es bastante viejo, 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.
Mirando los requisitos para std::make_shared
en 20.7.2.2.6 creación de shared_ptr [util.smartptr.shared.create], párrafo 1:
Requiere: La expresión
::new (pv) T(std::forward<Args>(args)...)
, dondepv
tiene tipovoid*
y apunta a un almacenamiento adecuado para contener un objeto de tipoT
, estará bien formada .A
debe ser un asignador (17.6.3.5). El constructor de copia y el destructor deA
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 toman en cuenta, creo que los trucos como la amistad son correctos.
Una solución simple es derivar de A
Esto no necesita requerir que 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>();
}
Posiblemente la solución más simple. Basado en la answer 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() {}
};
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() {};
};
Si también desea habilitar un constuctor 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");
}
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 todavía deja todo 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 extensible de forma clara y simple para los constructores que toman argumentos porque otras personas estaban proporcionando respuestas en ese sentido y parecían confundidas al respecto.
#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;
}
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_;
};