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 de «figuras»: una clase base llamada Shape y unas clases derivadas llamadas Circle, Square, y Triangle. En la clase base, cree una función virtual llamada draw(), y redefínala en las clases derivadas. Cree un array de punteros a objetos Shape creados en el montículo (y, por tanto, haga upcasting de los punteros), y llame a draw() a través de los punteros a la clase base para verificar el comportamiento de la función virtual. Si su depurador lo permite, ejecute el código paso a paso.

  2. Modifique el Ejercicio 1 de tal forma que draw() sea una función virtual pura. Intente crear un objeto de tipo Shape. Intente llamar a la función virtual pura dentro del constructor y mire lo que ocurre. Dejándola como una función virtual pura, dé una definición para draw().

  3. Ampliando el Ejercicio 2, cree una función que reciba un objeto Shape por valor e intente hacer un upcast de un objeto derivado pasándolo como argumento. Vea lo que ocurre. Arregle la función haciendo que reciba una referencia al objeto Shape.

  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 prepare(). Llame a prepare() dentro de tune().

  6. Cree una jerarquía de herencia de Rodent (roedores): Mouse, Gerbil, Hamster, etc. En la clase base, proporcione los métodos que son comunes a todos los roedores, y redefínalos en las clases derivadas para que tengan diferentes comportamientos dependiendo del tipo específico de roedor. Cree un array de punteros a Rodent, rellénelo 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<Rodent*> en vez de un array de punteros. Asegúrese de que la memoria se limpia correctamente.

  8. Empezando con la jerarquía anterior de Rodent, herede un BlueHamster de Hamster (sí, existe algo así, tuve uno cuando era niño), redefina los métodos de la clase base y muestre que el código que llama a los métodos de la clase base no necesita cambiar para adaptarse al nuevo tipo.

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

  10. A partir de la jerarquía Rodent anterior, modifique Rodent para convertirla en una clase base abstracta pura.

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

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

  13. En Early.cpp, convierta Pet en una clase base abstracta pura.

  14. En AddingVirtuals.cpp, haga que todas las funciones miembro de Pet sean 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 debe probar que las dos llamadas producen diferentes resultados.

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

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

  18. Cree una clase que tenga un atributo y una clase derivada que añada otro atributo. Escriba una función no miembro que reciba 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 después 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. Localice el código en ensamblador para la llamada a la función virtual, sígalo y explíquelo.

  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á consultarla en la guía de su librería C) para medir la diferencia entre una llamada virtual y una llamada no virtual. Será necesario realizar múltiples llamadas a cada función dentro de su bucle de medició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 imprima algo) y haciendo el destructor virtual. Cree objetos de las distintas subclases y hágales 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 redefina 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 puede llamar a las tres funciones a través de la base? Elimine la definición redefinida 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 puede saber si el compilador hace 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 redefinan clone() para devolver copias de sus tipos específicos. En el main(), cree objetos de sus dos tipos derivados y haga upcast de ellos, después llame a clone() para cada uno y verifique que las copias clonadas son de los subtipos correctos. Experimente con su función clone() de modo que devuelva el tipo base, y después intente devolver el tipo derivado exacto. ¿Se le ocurre alguna situación en la que sea necesario este último enfoque?

  26. Modifique OStackTest.cpp creando su propia clase y haciendo que herede también (herencia múltiple) de 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 OperatorPolymorphism.cpp.

  28. (Intermedio) Cree una clase base X sin atributos y sin constructor, pero con una función virtual. Cree una clase Y que herede de X, pero sin un constructor explícito. Genere código ensamblador y examínelo para determinar si se crea y se llama un constructor de X y, si eso ocurre, qué hace ese código. 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 dónde se asigna el VPTR dentro de cada constructor. ¿El compilador está usando el mecanismo virtual dentro del constructor? Explique por qué se sigue usando la versión local de la función.

  30. (Avanzado) Si las llamadas a funciones de un objeto pasado por valor no usaran ligadura estática, una llamada virtual podría acceder 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 qué ocurre si se pasa un objeto por valor.

  31. (Avanzado) Encuentre exactamente cuánto 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 averiguando los ciclos de reloj necesarios para una llamada simple frente a los necesarios para 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. ¿Ha obtenido uno o dos VPTR en la clase derivada?

  33. Cree una clase con atributos 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 dónde se encuentra alojado el VPTR del objeto.

  34. Imagine que las funciones virtuales no existen, y modifique Instrument4.cpp para que use dynamic_cast para hacer el equivalente de las llamadas virtuales. Explique por qué es una mala idea.

  35. Modifique StaticHierarchyNavigation.cpp para que en vez de usar el RTTI de C++ use su propio RTTI vía una función virtual en la clase base llamada whatAmI() y un enum type { Circles, Squares };.

  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.