15.14. Ejercicios

Las soluciones a los ejercicios se pueden encontrar en el documento electrónico titulado «The Thinking in C++ Annotated Solution Guide», disponible por poco dinero en www.BruceEckel.com.

  1. Cree una jerarquía simple "figura": una clase base llamada Figura y una clases derivadas llamadas Circulo, Cuadrado, y Triangulo. En la clase base, hay que hacer una función virtual llamada dibujar(), y sobreescribirla en las clases derivadas. Hacer un array de punteros a objetos Figura que se creen en el montón (heap) y que obligue a realizar upcasting de los punteros, y llamar a dibujar() a través de la clase base para verificar el comportamiento de las funciones virtuales. Si el depurador lo soporta, intente ver el programa paso a paso.

  2. Modifique el Ejercicio 1 de tal forma que dibujar() sea una función virtual pura. Intente crear un objeto de tipo Figura. Intente llamar a la función virtual pura dentro del constructor y mire lo que ocurre. Dejándolo como una función virtual pura cree una definición para dibujar().

  3. Aumentando el Ejercicio 2, cree una función que use un objeto Figura por valor e intente hacer un upcast de un objeto derivado como argumento. Vea lo que ocurre. Arregle la función usando una referencia a un objeto Figura.

  4. Modifique C14:Combined.cpp para que f() sea virtual en la clase base. Cambie el main() para que se haga un upcast y una llamada virtual.

  5. Modifique Instrument3.cpp añadiendo una función virtual preparar(). Llame a preparar() dentro de tune().

  6. Cree una jerarquía de herencia de Roedores: Raton, Gerbo, Hamster, etc. En la clase base, proporcione los métodos que son comunes a todos los roedores, y redefina aquellos en las clases derivadas para que tengan diferentes comportamientos dependiendo del tipo específico de roedor. Cree un array de punteros a Roedor, rellenelo con distintos tipos de roedores y llame a los métodos de la clase base para ver lo que ocurre.

  7. Modifique el Ejercicio 6 para que use un vector<Roedor*> en vez de un array de punteros. Asegurese que se hace un limpiado correcto de la memoria.

  8. Empezando con la jerarquía anterior de Roedor, herede un HamsterAzul de Hamster (si, existe algo así, tuve uno cuando era niño), sobreescriba los métodos de la clase base y muestre que el código que llama a los métodos de clase base no necesitan cambiar para adecuarse el nuevo tipo.

  9. A partir de la jerarquía Roedor anterior, añadaun destructor no virtual, cree un objeto de la Hamster usando new, haga un upcast del puntero a Roedor*, y borre el puntero con delete para ver si no se llama a los destructores en la jerarquía. Cambie el destructor a virtual y demuestre que el comportamiento es ahora correcto.

  10. Modifique Roedor para convertirlo en una clase base pura abstracta.

  11. Cree un sistema de control aéreo con la clase base Avion y varios tipos derivados. Cree una clase Torre con un vector<Avion*> que envie los mensajes adecuados a los distintos aviones que están bajo su control.

  12. Cree un modelo de invernadero heredando varios tipos de Plantas y construyendo mecanismos en el invernadero que se ocupen de las plantas.

  13. En Early.cpp, haga a Pet una clase base abstracta pura.

  14. En AddingVirtuals.cpp, haga a todas las funciones miembro de Pet virtuales puras, pero proporcione una definición para name(). Arregle Dog como sea necesario, usando la definición de name() que se encuentra en la clase base.

  15. Escriba un pequeño programa para mostrar la diferencia entre llamar a una función virtual dentro de una función miembro normal y llamar a una función virtual dentro de un constructor. El programa de probar que las dos llamadas producen diferentes resultados.

  16. Modifique VirtualsInDestructors.cpp por heredando una clase de Derived y sobreescribiendo f() y el destructor. En main(), cree y haga un upcast de un objeto de su nuevo tipo, después borrelo.

  17. Use el Ejercicio 16 y añada llamadas a f() en cada destructor. Explique que ocurre.

  18. Cree un clase que tenga un dato miembro y una clase derivada que añada otro dato miembro. Escriba una función no miembro que use un objeto de la clase base por valor e imprima el tamaño del objeto usando sizeof. En el main() cree un objeto de la clase derivada, imprima su tamaño, y llame a su función. Explique lo que ocurre.

  19. Cree un ejemplo sencillo de una llamada a una función virtual y genere su salida en ensamblador. Localize el código en ensamblador para la llamada a la función virtual y explique el código.

  20. Escriba una clase con una función virtual y una función no virtual. Herede una nueva clase, haga un objeto de esa clase, y un upcast a un puntero del tipo de la clase base. Use la función clock() que se encuentra en <ctime> (necesitará echar un vistazo a su librerí C) para medir la diferencia entre una llamada virtual y una llamada no virtual. Será necesario realizar multiples llamadas a cada función para poder ver la diferencia.

  21. Modifique C14:Order.cpp añadiendo una función virtual en la clase base de la macro CLASS (que pinte algo) y haciendo el destructor virtual. Cree objetos de las distintas subclases y hagales un upcast a la clase base. Verifique que el comportamiento virtual funciona y que se realiza de forma correcta la construcción y la destrucción del objeto.

  22. Escriba una clase con tres funciones virtuales sobrecargadas. Herede una nueva clase y sobreescriba una de las funciones. Cree un objeto de la clase derivada. ¿Se puede llamar a todas las funciones de la clase base a través del objeto derivado? Haga un upcast de la dirección del objeto a la base. ¿Se pueden llamar a las tres funciones a través de la base? Elimine la definición sobreescrita en la clase derivada. Ahora ¿Se puede llamar a todas las funciones de la clase base a través del objeto derivado?.

  23. Modifique VariantReturn.cpp para que muestre que su comportamiento funciona con referencias igual que con punteros.

  24. En Early.cpp, ¿Cómo se le puede indicar al compilador que haga la llamada usando ligadura estática o ligadura dinámica? Determine el caso para su propio compilador.

  25. Cree una clase base que contenga una función clone() que devuelva un puntero a una copia del objeto actual. Derive dos subclases que sobreescriban clone() para devolver copias de sus tipos específicos. En el main(), cree y haga upcast de sus dos tipos derivados, y llame a clone() para cada uno y verifique que las copias clonadas son de los subtipos correctos. Experimente con su función clone() para que se pueda ir al tipo base, y después intente regresar al tipo exacto derivado. ¿Se le ocurre alguna situación en la que sea necesario esta aproximación?

  26. Modifique OStackTest.cpp creando su propia clase, después haga multiple herencia con Object para crear algo que pueda ser introducido en la pila. Pruebe su clase en el main().

  27. Añada un tipo llamado Tensor a OperartorPolymorphism.cpp.

  28. (Intermedio) Cree una clase base X sin datos miembro y sin constructor, pero con una función virtual. Cree una Y que herede de X, pero sin un constructor explícito. Genere código ensamblador y examinelo para deteriminar si se crea y se llama un constructor de X y, si eso ocurre, qué código lo hace. Explique lo que haya descubierto. X no tiene constructor por defecto, entonces ¿por qué no se queja el compilador?

  29. (Intermedio) Modifique el Ejercicio 28 escribiendo constructores para ambas clases de tal forma que cada constructor llame a una función virtual. Genere el código ensamblador. Determine donde se encuentra asignado el VPTR dentro del constructor. ¿El compilador está usando el mecanismo virtual dentro del constructor? Explique por qué se sigue usando la version local de la función.

  30. (Avanzado) Si una función llama a un objeto pasado por valor si ligadura estática, una llamada virtual accede a partes que no existen. ¿Es posible? Escriba un código para forzar una llamada virtual y vea si se produce un cuelgue de la aplicación. Para explicar el comportamiento, observe que ocurre si se pasa un objeto por valor.

  31. (Avanzado) Encuentre exactamente cuanto tiempo más es necesario para una llamada a una función virtual buscando en la información del lenguaje ensamblador de su procesador o cualquier otro manual técnico y encontrando los pulsos de reloj necesarios para una simple llamada frente al número necesario de las instrucciones de las funciones virtuales.

  32. Determine el tamaño del VPTR (usando sizeof) en su implementación. Ahora herede de dos clases (herencia múltiple) que contengan funciones virtuales. ¿Se tiene una o dos VPTR en la clase derivada?

  33. Cree una clase con datos miembros y funciones virtuales. Escriba una función que mire en la memoria de un objeto de su clase y que imprima sus distintos fragmentos. Para hacer esto será necesario experimentar y de forma iterativa descubrir donde se encuentra alojado el VPTR del objeto.

  34. Imagine que las funciones virtuales no existen, y modifique Instrument4.cpp para que use moldeado dinámico para hacer el equivalente de las llamadas virtuales. Esplique porque es una mala idea.

  35. Modifique StaicHierarchyNavigation.cpp para que en vez de usar el RTTI de C++ use su propio RTTI via una función virtual en la clase base llamada whatAmI() y un enum type { Circulos, Cuadrados };.

  36. Comience con PointerToMemberOperator.cpp del capítulo 12 y demuestre que el polimorfismo todavía funciona con punteros a miembros, incluso si operator->* está sobrecargado.