14.10. Upcasting

Anteriormente en este capítulo vio cómo un objeto de una clase derivada de ifstream tiene todas las características y comportamientos de un objeto ifstream. En FName2.cpp, se podía llamar a cualquier función miembro de ifstream sobre un objeto FName2.

Sin embargo, el aspecto más importante de la herencia no es que proporcione funciones miembro a la nueva clase, sino la relación que se expresa entre la nueva clase y la clase base. Esta relación puede resumirse diciendo: «La nueva clase es un tipo de la clase existente».

Esta descripción no es sólo una forma fantasiosa de explicar la herencia: el compilador la soporta directamente. Como ejemplo, considere una clase base llamada Instrument que representa instrumentos musicales y una clase derivada llamada Wind. Dado que la herencia significa que todas las funciones de la clase base están también disponibles en la clase derivada, cualquier mensaje que se pueda enviar a la clase base también se puede enviar a la clase derivada. Así, si la clase Instrument tiene una función miembro play(), también la tendrán los instrumentos Wind. Esto significa que se puede decir con precisión que un objeto Wind es también un tipo de Instrument. El siguiente ejemplo muestra cómo soporta el compilador esta noción:

//: C14:Instrument.cpp
// Inheritance & upcasting
enum note { middleC, Csharp, Cflat }; // Etc.

class Instrument {
public:
  void play(note) const {}
};

// Wind objects are Instruments
// because they have the same interface:
class Wind : public Instrument {};

void tune(Instrument& i) {
  // ...
  i.play(middleC);
}

int main() {
  Wind flute;
  tune(flute); // Upcasting
} ///:~

Listado 14.17. C14/Instrument.cpp


Lo interesante de este ejemplo es la función tune(), que acepta una referencia a Instrument. Sin embargo, en main() se llama a la función tune() pasándole una referencia a un objeto Wind. Dado que C++ es muy estricto con la comprobación de tipos, parece extraño que una función que acepta un tipo acepte sin problemas otro tipo, hasta que uno se da cuenta de que un objeto Wind es también un objeto Instrument, y de que no hay ninguna función que tune() pueda llamar para un Instrument que no esté también en Wind (eso es lo que garantiza la herencia). Dentro de tune(), el código funciona para Instrument y para cualquier cosa derivada de Instrument, y el acto de convertir una referencia o puntero a Wind en una referencia o puntero a Instrument se denomina upcasting.

14.10.1. ¿Por qué «upcasting»?

La razón de este término es histórica y se basa en la forma en que tradicionalmente se han dibujado los diagramas de herencia de clases: con la raíz en la parte superior de la página, creciendo hacia abajo. (Por supuesto, puede dibujar sus diagramas de cualquier modo que le resulte útil.) El diagrama de herencia de Instrument.cpp es entonces:

Upcasting

Figura 14.1. Upcasting


Moldear de la clase derivada a la clase base supone desplazarse hacia arriba en el diagrama de herencia, por eso normalmente se le llama upcasting. El upcasting siempre es seguro porque se pasa de un tipo más específico a un tipo más general; lo único que le puede ocurrir a la interfaz de la clase es que pierda funciones miembro, no que las gane. Por eso el compilador permite el upcasting sin ningún molde explícito ni otra notación especial.