c++ - sobreescribir - poo virtual
Clase base abstracta para uso con punteros inteligentes(intrusive_ptr): manejo de herencia, polimorfismo, capacidad de clonación y retorno de los métodos de fábrica (2)
Requerimientos
- Estoy escribiendo una clase llamada
RCObject
, que significa "Objeto contado de referencia"; - La clase
RCObject
debe ser abstracta y debe ser la clase base de un marco ( EC++3 elemento 7); La creación de instancias de subclases de
RCObject
en la pila debe estar prohibida ( MEC++1 Artículo 27);[ AÑADIDO: ]
[Supongamos que
Bear
es una subclase concreta deRCObject
][
CE
aquí significa error de compilación]Bear b1; // Triggers C.E. (by using MEC++1 Item 27) Bear* b2; // Not allowed but no way to trigger C.E. intrusive_ptr<Bear> b3; // Recommended Bear* bs1 = new Bear[8]; // Triggers C.E. container< intrusive_ptr<RCObject> > bs2; // Recommended intrusive_ptr_container<RCObject> bs3; // Recommended class SomeClass { private: Bear m_b1; // Triggers C.E. Bear* m_b2; // Not allowed but no way to trigger C.E. intrusive_ptr<Bear> m_b3; // Recommended };
CLARIFICADO: se debe prohibir la declaración / devolución de punteros sin
RCObject
aRCObject
(y subclases) (Desafortunadamente, no creo que exista una forma práctica de imponerlo, es decir, provocar errores de compilación cuando los usuarios no los siguen). Ver código fuente de ejemplo en el Artículo 3 anterior;- Las instancias de
RCObject
subclases deRCObject
deben ser clonables al igual queCloneable
en Java. ( MEC++1 Ítem 25); Los usuarios que subclasifican
RCObject
deben poder escribir "Métodos de fábrica" para sus subclases. No debe haber pérdida de memoria incluso si el valor devuelto se ignora (no se asigna a una variable). Un mecanismo cercano a esto es laautorelease
en Objective-C;[ AGREGADO: cschwan y Kos señalaron que devolver un "puntero inteligente a
RCObject
" es suficiente para cumplir el requisito. ]CLARIFICADO: Las instancias de
RCObject
subclases deRCObject
deben poder estar contenidas en un contenedorstd::
oboost::
container adecuado. Principalmente necesito un contenedor "std::vector
like", un contenedor "std::set
like" y un contenedor "std::map
like". La línea de base es queintrusive_ptr<RCObject> my_bear = v[10];
y
m["John"] = my_bear;
trabajar como se espera
- El código fuente debe ser compilable usando un compilador de C ++ 98 con soporte limitado de C ++ 11 (Visual Studio 2008 y gcc 4.6, para ser exactos).
Más información
- Soy un principiante en Boost (Boost es tan grande que necesito algo de tiempo para familiarizarme con él. Es posible que existan soluciones listas para usar, pero existe una gran posibilidad de que no sea consciente de tal solución );
- Debido a consideraciones de rendimiento, me gustaría usar
intrusive_ptr
lugar deshared_ptr
, pero estoy abierto a ambos e incluso a cualquier otra sugerencia; - No sé si
make_shared()
, make_shared() , enable_shared_from_this() podría ayudar (por cierto, enable_shared_from_this() no parece ser altamente promocionado en Boost, ni siquiera se puede encontrar en la página principal del puntero inteligente ); - He oído hablar de "escribir asignadores personalizados", pero me temo que es demasiado complicado;
- Me pregunto si
RCObject
debería heredarse deboost::noncopyable
privadamente; - No pude encontrar ninguna implementación existente que cumpla con todos mis requisitos;
- El marco es un motor de juego;
- Las principales plataformas de destino son Android y iOS;
- Sé
intrusive_ptr_add_ref()
yintrusive_ptr_release()
y cómo implementarlas usando la búsqueda dependiente de argumentos (también conocida como Koenig Lookup) ; - Sé cómo usar
boost::atomic_size_t
conboost:intrusive_ptr
.
Definiciones de clase
namespace zoo {
class RCObject { ... }; // Abstract
class Animal : public RCObject { ... }; // Abstract
class Bear : public Animal { ... }; // Concrete
class Panda : public Bear { ... }; // Concrete
}
Versión "no inteligente" - createAnimal () [Método de fábrica]
zoo::Animal* createAnimal(bool isFacingExtinction, bool isBlackAndWhite) {
// I wish I could call result->autorelease() at the end...
zoo::Animal* result;
if (isFacingExtinction) {
if (isBlackAndWhite) {
result = new Panda;
} else {
result = new Bear;
}
} else {
result = 0;
}
return result;
}
Versión "no inteligente" - main ()
int main() {
// Part 1 - Construction
zoo::RCObject* object1 = new zoo::Bear;
zoo::RCObject* object2 = new zoo::Panda;
zoo::Animal* animal1 = new zoo::Bear;
zoo::Animal* animal2 = new zoo::Panda;
zoo::Bear* bear1 = new zoo::Bear;
zoo::Bear* bear2 = new zoo::Panda;
//zoo::Panda* panda1 = new zoo::Bear; // Should fail
zoo::Panda* panda2 = new zoo::Panda;
// Creating instances of RCObject on the stack should fail by following
// the method described in the book MEC++1 Item 27.
//
//zoo::Bear b; // Should fail
//zoo::Panda p; // Should fail
// Part 2 - Object Assignment
*object1 = *animal1;
*object1 = *bear1;
*object1 = *bear2;
//*bear1 = *animal1; // Should fail
// Part 3 - Cloning
object1 = object2->clone();
object1 = animal1->clone();
object1 = animal2->clone();
//bear1 = animal1->clone(); // Should fail
return 0;
}
Versión "inteligente" [¡Incompleta!]
/* TODO: How to write the Factory Method? What should be returned? */
#include <boost/intrusive_ptr.hpp>
int main() {
// Part 1 - Construction
boost::intrusive_ptr<zoo::RCObject> object1(new zoo::Bear);
boost::intrusive_ptr<zoo::RCObject> object2(new zoo::Panda);
/* ... Skip (similar statements) ... */
//boost::intrusive_ptr<zoo::Panda> panda1(new zoo::Bear); // Should fail
boost::intrusive_ptr<zoo::Panda> panda2(new zoo::Panda);
// Creating instances of RCObject on the stack should fail by following
// the method described in the book MEC++1 Item 27. Unfortunately, there
// doesn''t exist a way to ban the user from declaring a raw pointer to
// RCObject (and subclasses), all it relies is self discipline...
//
//zoo::Bear b; // Should fail
//zoo::Panda p; // Should fail
//zoo::Bear* pb; // No way to ban this
//zoo::Panda* pp; // No way to ban this
// Part 2 - Object Assignment
/* ... Skip (exactly the same as "non-smart") ... */
// Part 3 - Cloning
/* TODO: How to write this? */
return 0;
}
El código anterior ("Versión inteligente") muestra el patrón de uso esperado. No estoy seguro de si este patrón de uso sigue las mejores prácticas de uso de punteros inteligentes o no. Por favor corrígeme si no lo hace.
Preguntas similares
hacer shared_ptr no utilizar eliminar (la respuesta aceptada parece elegante. ¿Es algún tipo de "desasignador personalizado"? No estoy seguro de cómo se compara con
intrusive_ptr
en términos de tiempo y eficiencia de espacio)intrusive_ptr en c ++ 11 (La respuesta aceptada mencionó
make_shared()
y enable_shared_from_this() , pero no entiendo "pero eso no le permite administrar el tipo usando diferentes tipos de punteros inteligentes")¿Hay una manera de aumentar la eficiencia de shared_ptr almacenando el recuento de referencia dentro del objeto controlado? (Todavía digiriendo)
intrusive_ptr: ¿Por qué no se proporciona una clase base común? (Las respuestas no son lo suficientemente detalladas)
¿Es este un uso válido de intrusive_ptr? (Aprendí algo de ello, pero el foco de esta pregunta era "pasar un puntero en bruto a una función que acepta un puntero inteligente")
Referencia contando con un cliente de puntero intrusivo genérico (Este usó "CRTP" , pero me temo que las subclases adicionales me harán sentir dolor de cabeza; debería hacer
zoo::Panda
extienda desdezoo::Bear
solo, o que haga que se extienda a amboszoo::Bear
andintrusive_base<zoo::Panda>
?)El recuento de referencias incrustadas con Boost shared_ptr (La respuesta aceptada mencionó que si bien
std::enable_shared_from_this()
debería estar bien, enable_shared_from_this() parece tener algunos problemas)
Referencias
- [ EC++3 ]: C ++ efectivo: 55 formas específicas de mejorar sus programas y diseños (3ª edición) por Scott Meyers
- Ítem 7: Declarar destructores virtuales en clases base polimórficas.
- [ MEC++1 ]: C ++ más efectivo: 35 nuevas formas de mejorar sus programas y diseños (1ª edición) por Scott Meyers
- Ítem 25: Virtualización de constructores y funciones no miembros.
- Artículo 27: Requerir o prohibir objetos basados en el montón.
Artículos
- [ boost::atomic_size_t ]: ejemplos de uso de
boost::atomic
- Boost.org- [ section ]: recuento de referencias
- La creación de instancias de subclases
RCObject
en la pila debe estar prohibida ([MEC ++ 1] [mec ++ 1], elemento 27);
¿Cuál es tu razón de ser? MEC ++ da el ejemplo de "los objetos pueden cometer suicidio", lo que puede tener sentido en el contexto de un marco de juego. ¿Es ese el caso?
Debería ser posible hacerlo con un puntero inteligente lo suficientemente inteligente, si insiste en evitar una solución más sencilla.
Tenga en cuenta que si ese es el caso, es probable que también desee deshabilitar la creación de matrices de tales objetos en la pila con el new[]
; esto también evita que se elimine uno solo. Probablemente también querrá rechazar el uso de RCObjects como subobjetos (miembros en otras clases). Esto significaría que no está utilizando los valores de RCObject por completo y que el código del cliente solo los maneja a través de punteros inteligentes.
- Debe evitarse declarar / devolver punteros en bruto a
RCObject
(y subclases) (Desafortunadamente, no creo que exista una manera de imponerlo mediante la emisión de errores de compilación);
Entonces es probable que tengas punteros débiles para tener una forma de decir "Estoy interesado en este objeto, pero no lo estoy manteniendo vivo".
- Los usuarios que subclasifican
RCObject
deberían poder escribir ["Métodos de fábrica"] [método_fábrica] para sus subclases. No debe haber pérdida de memoria incluso si el valor devuelto se ignora (no se asigna a una variable).
Dicha función devolvería un objeto de puntero inteligente temporal con un recuento de referencia igual a 1. Si este temporal no se utiliza para inicializar otro (incrementando así el recuento de referencia), limpiará el objeto. Estás seguro.
- Las instancias de
RCObject
subclases deRCObject
deben poder estar contenidas en unstd::
oboost::
container (o lo que sea apropiado). Principalmente necesito algo similar astd::vector
,std::set
ystd::map
;
Este tipo de desacuerdo con (3). Si insiste en que los objetos tengan que crearse individualmente en el montón y pasar a través de punteros inteligentes (no como valores), entonces también debe usar contenedores de punteros inteligentes.
- Debido a consideraciones de rendimiento, me gustaría usar [
intrusive_ptr
] [intrusive_ptr] en lugar de [shared_ptr
] [shared_ptr], pero estoy abierto a ambos e incluso a otras sugerencias;
¿No estás optimizando prematuramente?
Además, creo que el uso de punteros intrusivos elimina la posibilidad de usar referencias débiles, que es muy probable que necesite, como mencioné anteriormente.
- Me pregunto si
RCObject
debería heredarse de [boost::noncopyable
] [noncopyable] de forma privada;
Si no está permitiendo las variables de tipo de valor y proporciona un Clon virtual, entonces probablemente no hay necesidad de un constructor de copia pública. Puede crear un ctor de copia privada y utilizarlo al definir Clon.
make_shared
crea una instancia de su clase en el mismo bloque de asignación que el contador de referencia. No estoy seguro de por qué cree que intrusive_ptr
tendrá un mejor rendimiento: es genial cuando ya existe una maquinaria de conteo de referencias que no puede eliminar, pero este no es el caso aquí.
Para la clonación, lo implementaría como una función gratuita que toma un controlador inteligente y devuelve lo mismo. Es un amigo, y llama a un método de clonación virtual puro privado en la base que devuelve un puntero compartido a la base, luego hace un puntero inteligente rápido para convertir el puntero compartido en un derivado. Si prefieres la clonación como método, usa crtp para duplicar esto (dándole al clon privado un nombre como secret_clone
). Esto le da tipos de retorno de puntero inteligente covariante con poca sobrecarga.
Crtp con un rango de clases base a menudo le hace pasar tanto las clases base como las derivadas. La clase crtp deriva de la base y tiene el self()
habitual que devuelve derivado.
Las funciones de fábrica deben devolver el puntero inteligente. Puede usar el truco de eliminación de contenido personalizado para obtener una llamada a Metid anterior a la destrucción para la última limpieza.
Si está completamente paranoico, puede bloquear la mayoría de las formas para obtener un puntero en bruto o una referencia a su clase: operador de bloque * en el puntero inteligente. Entonces, la única ruta a la clase en bruto es una llamada explícita al operator->
.
Otro enfoque a considerar es unique_ptr
y referencias a los mismos. ¿Necesita propiedad compartida y gestión de por vida? Hace algunos problemas más simples (propiedad compartida).
Tenga en cuenta que los punteros débiles que cuelgan impiden que la memoria se comparta para traerla reciclada.
Una desventaja seria de usar punteros inteligentes siempre es que no puede tener instancias de pila o instancias directamente dentro de contenedores. Ambos pueden ser serios aumentos de rendimiento.