Cuando se crea un objeto que contiene funciones virtuales, su VPTR debe ser inicializado para apuntar a la correcta VTABLE. Esto debe ser hecho antes de que exista la oportunidad de llamar a una función virtual. Como se puede adivinar, debido a que el constructor tiene el trabajo de traer a la existencia al objeto, también será trabajo del constructor inicializar el VPTR. El compilador de forma secreta añade código al principio del constructor para inicializar el VPTR. Y como se describe en el capítulo 14, si no se crea un constructor de una clase de forma explícita, el compilador genera uno de forma automática. Si la clase tiene funciones virtuales, el constructor incluirá el código apropidado para la inicialización del VPTR. Esto tiene varias consecuencias.
La primera concierne a la eficiencia. La razón de que existan
funciones inline
es reducir la sobrecarga que
produce llamar a funciones pequeñas. Si C++ no proporciona
funciones inline
, el preprocesador debe ser
usado para crear estas "macros". Sin embargo, el preprocesador no
tiene los conceptos de accesos o clases, y además no puede ser
usado para crear macros con funciones miembro. Además, con los
constructores que deben tener código oculto insertado por el
compilador, una macro del preprocesador no funcionaría del todo.
Hay que estar precavidos cuando se estén buscando agujeros de
eficiencia porque el compilador está insertando código oculto en
los constructores. No sólo hay que inicializar el VPTR, también
hay que comprobar el valor de this
(en caso de
que el operador new
devuelva cero), y llamar
al constructor de la clase base. Todo junto, éste código puede
tener cierto impacto cuando se pensaba que era una simple función
inline
. En particular, el tamaño del
constructor puede aplastar al ahorro que se consigue al reducir la
sobrecarga en las llamadas. Si se hacen un monton de llamadas a
constructores inline, el tamaño del código puede crecer sin ningún
beneficio en la velocidad.
Cuando esté afinando el código recuerde considerar el quitar los constructores en línea.
La segunda faceta interesante de los constructores y las funciones virtuales tiene que ver con el orden de las llamadas a los constructores y el modo en que las llamadas virtuales se hacen dentro de los constructores.
Todos los constructores de la clase base son siempre llamados en el constructor de una clase heredada. Esto tiene sentido porque el constructor tiene un trabajo especial: ver que el objeto está construido de forma apropiada. Una clase derivada sólo tiene acceso a sus propios miembros, y no a los de la clase base. únicamente el constructor de la clase base puede inicializar de forma adecuada a sus propios elementos. Por lo tanto es esencial que se llame a todos los constructores; de otra forma el objeto no estará construido de forma adecuada. Esto es por lo que el compilador obliga a hacer una llamada por cada trozo en una clase derivada. Se llamará al constructor por defecto si no se hace una llamada explícita a un constructor de la clase base. Si no existe constructor por defecto, el compilador lo creará.
El orden de las llamadas al constructor es importante. Cuando se
hereda, se sabe todo sobre la clase base y se puede acceder a
todos los miembros públicos y protegidos
(public
y protected
) de la
clase base. ésto significa que se puede asumir que todos los
miembros de la clase base son válidos cuando se está en la clase
derivada. En una función miembro normal, la construcción ya ha
ocurrido, por lo que todos los miembros de todas las partes del
objeto ya han sido construidos. Dentro del constructor, sin
embargo, hay que asumir que todos los miembros que se usen han
sido construidos. La única manera de garantizarlo es llamando
primero al constructor de la clase base. Entonces cuando se esté
en el constructor de la clase derivada, todos los miembros a los
que se pueda acceder en la clase base han sido
inicializados. "Saber que todos los miembros son válidos" dentro
del constructor es también la razón por la que, dentro de lo
posible, se debe inicializar todos los objetos miembros (es
decir, los objetos puestos en la clase mediante composición). Si
se sigue ésta práctica, se puede asumir que todos los miembros
de la clase base y los miembros objetos del objeto actual han
sido inicializados.