15.10. funciones virtuales y constructores

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.

15.10.1. Orden de las llamadas a los constructores

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.