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.
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.
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()
.
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
.
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.
Modifique Instrument3.cpp
añadiendo una
función virtual preparar()
. Llame a
preparar()
dentro de
tune()
.
Cree una jerarquía de herencia de
Roedor
es: 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.
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.
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.
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.
Modifique Roedor
para convertirlo en una
clase base pura abstracta.
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.
Cree un modelo de invernadero heredando varios tipos de
Planta
s y construyendo mecanismos en el
invernadero que se ocupen de las plantas.
En Early.cpp
, haga a
Pet
una clase base abstracta pura.
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.
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.
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.
Use el Ejercicio 16 y añada llamadas a f()
en cada destructor. Explique que ocurre.
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.
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.
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.
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.
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?.
Modifique VariantReturn.cpp
para que
muestre que su comportamiento funciona con referencias igual
que con punteros.
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.
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?
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()
.
Añada un tipo llamado Tensor
a
OperartorPolymorphism.cpp
.
(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?
(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.
(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.
(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.
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?
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.
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.
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 };
.
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.