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_shared
creación de instancias de plantilla 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 sea señalado por a ::std::shared_ptr
. ¿Hay alguna forma de lograr esto?
c++
c++11
shared-ptr
De todo género
fuente
fuente
Respuestas:
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:
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.
fuente
protected
lugar deprivate
. Y por "eso", me estoy refiriendo a lathis_is_private
clase, que tal vez debería cambiarse de nombre en tal caso. Usualmente lo llamoconstructor_access
en mi código.{}
la etiqueta privada sin tener acceso al nombre del tipo (probado con g ++ 4.9.0). Sin parámetros reales, intenta construir aA
partir 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.this_is_private
un ctor privado, puedes hacer de la clase A un amigo. Parece cerrar la escapatoria.Mirando los requisitos para
std::make_shared
en 20.7.2.2.6 shared_ptr creation [util.smartptr.shared.create], párrafo 1: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 hacerA
una interfaz o incluso un tipo polimórfico.fuente
shared_ptr
almacena un borrador en el momento de la creación de instancias, y si está usandomake_shared
el borrador absolutamente tiene que estar usando el tipo correcto.Posiblemente la solución más simple. Basado en la respuesta anterior de Mohit Aron e incorporando la sugerencia de dlf.
fuente
A
tiene 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 seanA
visibles comomake_shared_enabler
constructores. Usar la característica de herencia de constructores (using A::A;
) parece que no ayuda aquí porque los constructores seguirán siendo privados.class A { ... private: struct A_shared_enabler; }; class A::A_shared_enabler : public A { ... }
. Ver aquí cpp.sh/65qbr .Aquí hay una buena solución para esto:
fuente
MakeSharedEnabler
localmente dentroA::Create()
.¿Qué tal esto?
fuente
::std::make_shared
tiene 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
.fuente
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í:
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 ().
fuente
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)
Uso
Si / cuando lo anterior se agrega al estándar, simplemente haríamos:
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.
fuente
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:
He probado en Windows y Linux, es posible que necesite ajustes para diferentes plataformas.
fuente
std::shared_ptr_access
al estándar, que podría considerarse que permite hacer lo anterior de una manera simple y portátil.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í
Desafortunadamente, llamar
std::make_shared<B>()
desde un métodoA
hará que el compilador se queje deB::B()
ser privado.Mi solución a esto es crear una
Pass
clase ficticia pública (igual quenullptr_t
) dentroB
que tenga un constructor privado y sea amigoA
y haga queB
el constructor sea público y se agreguePass
a sus argumentos, de esta manera.fuente
Si también desea habilitar un constructor que tome argumentos, esto puede ayudar un poco.
fuente
[Editar] Leí el hilo mencionado anteriormente en una
std::shared_ptr_access<>
propuesta estandarizada . Dentro hubo una respuesta que señalaba una soluciónstd::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 constd::enable_shared_from_this<>
, por lo que obviamente sería preferible a mi solución original en esta respuesta. Aquí está...[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.
Tenga en cuenta que el enfoque anterior no funciona bien
std::enable_shared_from_this<>
porque la inicialstd::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 ...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.
fuente
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:
fuente
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.
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
Entonces su declaración de clase parece bastante simple. Puede hacer una sola macro para declarar el ptr y la clase si lo prefiere.
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.
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.
fuente
std::_Ref_count_obj
Es 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.Puedes usar esto:
fuente
std::make_shared
.fuente