virtuales sobreescribir puro poo polimorfismo métodos modificador metodo herencia heredadas ejemplos clases c++ inheritance boost polymorphism reference-counting

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

  1. Estoy escribiendo una clase llamada RCObject , que significa "Objeto contado de referencia";
  2. La clase RCObject debe ser abstracta y debe ser la clase base de un marco ( EC++3 elemento 7);
  3. 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 de RCObject ]

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

  4. CLARIFICADO: se debe prohibir la declaración / devolución de punteros sin RCObject a RCObject (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;

  5. Las instancias de RCObject subclases de RCObject deben ser clonables al igual que Cloneable en Java. ( MEC++1 Ítem ​​25);
  6. 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 la autorelease en Objective-C;

    [ AGREGADO: cschwan y Kos señalaron que devolver un "puntero inteligente a RCObject " es suficiente para cumplir el requisito. ]

  7. CLARIFICADO: Las instancias de RCObject subclases de RCObject deben poder estar contenidas en un contenedor std:: o boost:: 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 que

    intrusive_ptr<RCObject> my_bear = v[10];

    y

    m["John"] = my_bear;

    trabajar como se espera

  8. 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 de shared_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 de boost::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;
  • intrusive_ptr_add_ref() y intrusive_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 con boost: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 desde zoo::Bear solo, o que haga que se extienda a ambos zoo::Bear and intrusive_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

  • [ CP8394 ]: punteros inteligentes para mejorar su código - CodeProject
    • [ section ]: intrusive_ptr - puntero compartido ligero
  • [ DrDobbs ]: una clase base para objetos de referencia intrusivamente contados en C ++ - Dr. Dobb''s

  1. 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.

  1. 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".

  1. 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.

  1. Las instancias de RCObject subclases de RCObject deben poder estar contenidas en un std:: o boost:: container (o lo que sea apropiado). Principalmente necesito algo similar a std::vector , std::set y std::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.