Para que la ligadura dinámica tenga efecto en una función
particular, C++ necesita que se use la palabra reservada
virtual
cuando se declara la función en la
clase base. La ligadura en tiempo de ejecución funciona
unícamente con las funciones virtual
es, y sólo
cuando se está usando una dirección de la clase base donde exista
la función virtual
, aunque puede ser definida
también en una clase base anterior.
Para crear una función miembro como virtual
,
simplemente hay que preceder a la declaración de la función con la
palabra reservada virtual
. Sólo la declaración
necesita la palabra reservada virtual
, y no la
definición. Si una función es declarada como
virtual
, en la clase base, será entonces
virtual
en todas las clases derivadas. La
redefinición de una función virtual
en una
clase derivada se conoce como overriding.
Hay que hacer notar que sólo es necesario declarar la función como
virtual
en la clase base. Todas las funciones
de las clases derivadas que encajen con la declaración que esté en
la clase base serán llamadas usando el mecanismo virtual. Se
puede usar la palabra reservada
virtual
en las declaraciones de las clases
derivadas (no hace ningún mal), pero es redundante y puede causar
confusión.
Para conseguir el comportamiento deseado de
Instrument2.cpp
, simplemente hay que añadir la
palabra reservada virtual
en la clase base
antes de play()
.
//: C15:Instrument3.cpp // Late binding with the virtual keyword #include <iostream> using namespace std; enum note { middleC, Csharp, Cflat }; // Etc. class Instrument { public: virtual void play(note) const { cout << "Instrument::play" << endl; } }; // Wind objects are Instruments // because they have the same interface: class Wind : public Instrument { public: // Override interface function: void play(note) const { cout << "Wind::play" << endl; } }; void tune(Instrument& i) { // ... i.play(middleC); } int main() { Wind flute; tune(flute); // Upcasting } ///:~
Listado 15.2. C15/Instrument3.cpp
Este archivo es idéntico a Instrument2.cpp
excepto por la adición de la palabra reservada
virtual
y, sin embargo, el comportamiento es
significativamente diferente: Ahora la salida es
Wind::play
.
Con play()
definido como
virtual
en la clase base, se pueden añadir
tantos nuevos tipos como se quiera sin cambiar la función
play()
. En un programa orientado a objetos
bien diseñado, la mayoría de las funciones seguirán el modelo de
play()
y se comunicarán únicamente a través
de la interfaz de la clase base. Las funciones que usen la
interfaz de la clase base no necesitarán ser cambiadas para
soportar a las nuevas clases.
Aquí está el ejemplo de los instrumentos con más funciones
virtuales y un mayor número de nuevas clases, las cuales
trabajan de manera correcta con la antigua (sin modificaciones)
función play()
:
//: C15:Instrument4.cpp // Extensibility in OOP #include <iostream> using namespace std; enum note { middleC, Csharp, Cflat }; // Etc. class Instrument { public: virtual void play(note) const { cout << "Instrument::play" << endl; } virtual char* what() const { return "Instrument"; } // Assume this will modify the object: virtual void adjust(int) {} }; class Wind : public Instrument { public: void play(note) const { cout << "Wind::play" << endl; } char* what() const { return "Wind"; } void adjust(int) {} }; class Percussion : public Instrument { public: void play(note) const { cout << "Percussion::play" << endl; } char* what() const { return "Percussion"; } void adjust(int) {} }; class Stringed : public Instrument { public: void play(note) const { cout << "Stringed::play" << endl; } char* what() const { return "Stringed"; } void adjust(int) {} }; class Brass : public Wind { public: void play(note) const { cout << "Brass::play" << endl; } char* what() const { return "Brass"; } }; class Woodwind : public Wind { public: void play(note) const { cout << "Woodwind::play" << endl; } char* what() const { return "Woodwind"; } }; // Identical function from before: void tune(Instrument& i) { // ... i.play(middleC); } // New function: void f(Instrument& i) { i.adjust(1); } // Upcasting during array initialization: Instrument* A[] = { new Wind, new Percussion, new Stringed, new Brass, }; int main() { Wind flute; Percussion drum; Stringed violin; Brass flugelhorn; Woodwind recorder; tune(flute); tune(drum); tune(violin); tune(flugelhorn); tune(recorder); f(flugelhorn); } ///:~
Listado 15.3. C15/Instrument4.cpp
Se puede ver que se ha añadido otro nivel de herencia debajo de
Wind
, pero el mecanismo
virtual
funciona correctamente sin importar
cuantos niveles haya. La función adjust()
no está redefinida
(override) por
Brass
y
Woodwind
. Cuando esto ocurre, se usa la
definición más "cercana" en la jerarquía de herencia - el
compilador garantiza que exista alguna
definición para una función virtual, por lo que nunca acabará en
una llamada que no esté enlazada con el cuerpo de una función
(lo cual sería desatroso).
El array A[]
contiene punteros a la clase
base Instrument
, lo que implica que
durante el proceso de inicialización del array habrá
upcasting. Este array y la
función f()
serán usados en posteriores
discusiones.
En la llamada a tune()
, el
upcasting se realiza en cada tipo
de objeto, haciendo que se obtenga siempre el comportamiento
deseado. Se puede describir como "enviar un mensaje a un objeto
y dejar al objeto que se preocupe sobre qué hacer con él". La
función virtual
es la lente a usar cuando
se está analizando un proyecto: ¿Dónde deben estar las clases
base y cómo se desea extender el programa?
Sin embargo, incluso si no se descubre la interfaz apropiada
para la clase base y las funciones virtuales durante la creación
del programa, a menudo se descubrirán más tarde, incluso mucho
más tarde cuando se desee ampliar o se vaya a hacer funciones de
mantenimiento en el programa. Esto no implica un error de
análisis o de diseño; simplemente significa que no se conocía o
no se podía conocer toda la información al principio. Debido a
la fuerte modularización de C++, no es mucho problema que esto
suceda porque los cambios que se hagan en una parte del sistema
no tienden a propagarse a otras partes como sucede en C.