Diferencia entre revisiones de «Programación en C++/Funciones virtuales»

Contenido eliminado Contenido añadido
Ortografía
Etiqueta: editor de código 2017
Línea 49:
 
En este ejemplo, 'p' puede ser usado para acceder a todos los
elementos de objD heredados de objB. Sin embargo elementos especificosespecíficos
a objD no pueden ser referenciados con 'p'.
 
Para un ejemplo mas concreto, considere el siguiente programa, el cual
define una clase llamada clase_B y una clase derivada llamada clase_D.
Este programa usa una simple jerarquiajerarquía para almacenar autores y
títulos.
titulos.
 
<source lang="cpp">
Línea 135:
clase_D y puede ser usado para acceder aquellos elementos de la clase
derivada que son heredados de la clase base. Pero recuerde, un puntero
base no puede acceder aquellos elementos especificosespecíficos de la clase
derivada. De ahiahí el porqueporqué de que mostrar_titulo() es accesada con el
puntero dp, el cual es un puntero a la clase derivada.
 
Línea 142:
usando un puntero de clase base, se debe hacer un casting hacia el
puntero del tipo derivado. Por ejemplo, esta linea de codigo llamara
apropiadamente a la funcionfunción mostrar_titulo() en objD:
 
<source lang="cpp">
Línea 149:
 
 
Los parentesisparéntesis externos son necesarios para asociar el cast con 'p y
no con el tipo de retorno de mostrar_titulo(). Aunque no hay nada
tecnicamentetécnicamente erroneoerróneo con castear un puntero de esta manera, es
probablemente mejor evitarlo, ya que este simplemente agrega confusionconfusión
a sus codigocódigo. ( En realidad, la mayoriamayoría de los programadores de C++
consideran esto como mala forma.)
 
Línea 162:
 
Un puntero es incrementado y decrementado relativamente a su tipo base.
Por lo tanto, cuando un puntero de la clase base estaestá apuntado a
un objeto derivado, incrementarlo o decrementarlo no harahará que apunte al
siguiente objeto de la clase derivada. En vez de eso, apuntara a
( lo que piense que es ) el proximopróximo objeto de la clase base. Por lo
tanto, deberiadebería considerar invalido incrementar o decrementar un
puntero de clase base cuando esta apuntando a un objeto derivado.
 
El hecho de que un puntero a un tipo base pueda ser usado para apuntar
a cualquier objeto derivado de la base es extremadamente importante,
y fundamental para C++. Como aprenderaaprenderá muy pronto, esta flexibilidad
es crucial para la manera en que C++ implementa su polimorfismo en
tiempo de ejecucionejecución.
 
=== REFERENCIAS A TIPOS DERIVADOS ===
Línea 179:
Similar a la acción de punteros ya descritas, una referencia a la
clase base puede ser usada para referirse a un objeto de un tipo
derivado. La masmás comuncomún aplicacionaplicación de esto es encontrada en los
parametrosparámetros de la funciones. Un parametroparámetro de referencia de la clase
base puede recibir objetos de la clase base asiasí como tambientambién otro
tipo derivado de esa misma base.
 
=== FUNCIONES VIRTUALES ===
 
El polimorfismo en tiempo de ejecucionejecución es logrado por una combinacioncombinación
de dos caracteristicascaracterísticas: 'Herencia y funciones virtuales". AprendioAprendió
sobre la herencia en el capitulocapítulo precedente. Aqui, aprendera sobre
funcionfunción virtual.
 
Una función virtual es una funcionfunción que es declarada como 'virtual' en
una clase base y es redefinida en una o masmás clases derivadas. AdemasAdemás,
cada clase derivada puede tener su propia versionversión de la funcionfunción
virtual. Lo que hace interesantes a las funciones virtuales es que
sucede cuando una es llamada a travestravés de un puntero de clase base
( o referencia ). En esta situacionsituación, C++ determina a cual version de
la funcionfunción llamar basandosebasándose en el tipo de objeto apuntado por el
puntero. Y, esta determinaciondeterminación es hecha en 'tiempo de ejecucionejecución'.
AdemasAdemás, cuando diferentes objetos son apuntados, diferentes versiones
de la funcionfunción virtual son ejecutadas. En otras palabras es el tipo
de objeto al que estaestá siendo apuntado ( no el tipo del puntero ) lo
que determina cualcuál versionversión de la funcionfunción virtual seraserá ejecutada.
AdemasAdemás, si la clase base contiene una funcionfunción virtual, y si dos o mas
diferentes clases son derivadas de esa clase base, entonces cuando
tipos diferentes de objetos estanestán siendo apuntados a travestravés de un
puntero de clase base, diferentes versiones de la funcionfunción virtual
son ejecutadas. Lo mismo ocurre cuando se usa una referencia a la
clase base.
 
Se declara una funcionfunción como virtual dentro de la clase base precediendo
su declaraciondeclaración con la palabra clave virtual. Cuando una funcionfunción
virtual es redefinida por una clase derivada, la palabra clave
'virtual' no necesita ser repetida ( aunque no es un error hacerlo ).
 
Una clase que incluya una funcionfunción virtual es llamada una
'clase polimorficapolimórfica'. Este terminotérmino tambientambién aplica a una clase que
hereda una clase base conteniendo una funcionfunción virtual.
 
Examine este corto programa, el cual demuestra el uso de funciones
Línea 277:
Examinemos el programa en detalle para comprender como funciona:
 
En 'base', la funcionfunción 'quien()' es declarada como 'virtual'. Esto
significa que la funcionfunción puede ser redefinida en una clase derivada.
Dentro de ambas 'primera_d' y 'segunda_d', 'quien()' es redefinida
relativa a cada clase. Dentro de main(), cuatro variables son
declaradas: 'obj_base', el cual es un objeto del tipo 'base'; 'p'
el cual un un puntero a objetos del tipo 'base'; 'obj_primera' y
'obj_segunda', que son objetos de dos clases derivadas. A continuacioncontinuación
'p' es asignada con la direcciondirección de 'obj_base', y la funcionfunción quien()
es llamada. Como quien() es declarada como virtual, C++ determina
en tiempo de ejecucionejecución, cual version de quien() es referenciada por el
tipo del objeto apuntado por 'p'. En este caso, 'p' apunta a un objeto
del tipo 'base', asiasí que es la versionversión de 'quien()' declarada en
'base' la que es ejecutada. A continuacioncontinuación, se le asigna a 'p' la
direcciondirección de 'obj_primera'. Recuerde que un puntero de la clase base
puede referirse a un objeto de cualquier clase derivadas. Ahora,
cuando quien() es llamada, C++ nuevamente comprueba para ver que tipo
de objeto es apuntado por 'p' y basado en su tipo, determina cual
versionversión de quien() llamar. Como 'p' apunta a un objeto del tipo
'obj_primera', esa versionversión de quien() es usada. De ese mismo modo,
cuando 'p' es asignada con la direcciondirección de 'obj_segunda', la version
de quien() declarada en 'segunda_d' es ejecutada.
 
 
RECUERDE: "Es determinado en tiempo de ejecucionejecución cual version de una
funcionfunción virtual en realidad es llamada. AdemasAdemás, esta determinaciondeterminación es
basada solamente en el tipo del objeto que estaestá siendo apuntado por
un puntero de clase base."
 
Una funcionfunción virtual puede ser llamada normalmente, con la sintaxis
del operador estandarestándar de 'objeto.' Esto quiere decir que en el
ejemplo precedente, no seriasería incorrecto sintacticamentesintácticamente acceder a
quien() usando esta declaraciondeclaración:
 
<source lang="cpp">
Línea 314:
</source>
 
Sin embargo, llamar a una funcionfunción virtual de esta manera ignora sus
atributos polimorficospolimórficos. Es solo cuando una funcionfunción virtual es accesada
por un puntero de clase base que el polimorfismo en tiempo de
ejecucionejecución es logrado.
 
A primera vista, la redefinicionredefinición de una funcionfunción virtual en una clase
derivada parece ser una forma especial de sobrecarga de funcionfunción. Sin
embargo, este no es el caso. De hecho, los dos procesos son
fundamentalmente diferentes. Primero, una funcionfunción sobrecargada debe
diferir en su tipo y/o numeronúmero de parametrosparámetros, mientras que una funcionfunción
virtual redefinida debe tener exactamente el mismo tipo y numeronúmero
de parametrosparámetros. De hecho, los prototipos para una funcionfunción virtual y sus
redefiniciones debe ser exactamente los mismos. Si los prototipos
difieren, entonces la funcionfunción simplemente se considera sobrecargada,
y su naturaleza virtual se pierde. Otra restriccionrestricción es que una funcionfunción
virtual debe ser un miembro, no una funcionfunción 'friend', de la clase
para la cual es definida. Sin embargo, una funcionfunción virtual puede
ser una funcionfunción 'friend' de otra clase. TambienTambién, es permisible para
funciones destructores ser virtuales, pero esto no es asiasí para
los constructores.
 
Línea 347:
 
Por las restricciones y diferencias entre sobrecargar funciones
normales y redefinir funciones virtuales, el terminotérmino 'overriding'
es usado para describir la redefinicionredefinición de una funcionfunción virtual.
 
=== LAS FUNCIONES VIRTUALES SON HEREDADAS ===
 
Una vez que una funcionfunción es declarada como virtual, esta se mantiene
virtual sin importar cuantas capas de clases derivadas esta debe
perdurar. Por ejemplo, si 'segunda_d' es derivada de 'primera_d'
en vez de 'base', como se muestra en el proximopróximo ejemplo, entonces
quien() es aun virtual y la versionversión apropiada es aun correctamente
seleccionada.
 
Línea 369:
</source>
 
Cuando una clase derivada no redefine una funcionfunción virtual, entonces
la funcionfunción, como se definiciodefine en la clase base, es usada. Por ejemplo
intente esta versionversión del programa precedente en el cual 'segunda_d'
no redefine 'quien()':
 
Línea 429:
 
Como confirma la salida, como 'quien()' no ha sido redefinida por
'segunda_d', entonces 'p' apunta a 'obj_segunda', es la versionversión de
'quien()' en 'base' la que es ejecutada.
 
Mantenga en mente que las caracteristicascaracterísticas heredadas de 'virtual'
son jeraquicasjerárquicas. Por tanto,, si el ejemplo precendenteprecedente es modificado
para que 'segunda_d' sea derivada de 'primera_d' en vez de 'base',
entonces cuando quien() es referenciada relativa a un objeto del tipo
'segunda_d', es la versionversión de 'quien()' declarada dentro de
primera_d' la que es llamada ya que es la clase mas cercana a
'segunda_d', no 'quien()' dentro de base. El siguiente programa
demuestra esta jerarquiajerarquía.
 
<source lang="cpp">
Línea 495:
</pre>
 
Como puede ver, 'segunda_d' ahora usa la versionversión 'quien()' de
'primera_d' porque esa versionversión es la mas cercana en la cadena de
herencia.
 
=== PORQUE FUNCIONES VIRTUALES ===
 
Como se declaraba en el inicio de este capítulo, las funciones virtuales en combinación con tipos derivados le permiten a C++ soportar polimorfismo en tiempo de ejecución. El polimorfismo es esencial para la programación orientada a objetos por una razón: Esta permite a una clase generalizada especificar aquellas funciones que serán comunes a todas las derivadas de esa clase, mientras que permite a una clase derivada definir la implementación específica de algunas o todas de esas funciones. A veces esta idea es expresada como: La clase base dicta la 'interface' general que cualquier objeto derivado de esa clase tendrá, pero permite a la clase derivada definir el método actual usado para implementar esa interfaceinterfaz. De ahí que la frase "una interface múltiples métodos" sea usada a menudo para describir el polimorfismo.
 
Parte del truco de aplicar el polimorfismo de una manera satisfactoria es comprender que la clase base y derivada forman una jerarquía, la cual se mueve de mayor a menor generalización (base a derivada). Diseñada correctamente, la clase base provee todos los elementos que una clase derivada puede usar directamente. También define cuales funciones la clase derivada debe implementar por su cuenta. Esto permite a la clase derivada la flexibilidad para definir sus propios métodos, y aun mantener un interface consistente. Eso es, como la forma de la interface es definida por la clase base, cualquier clase derivada compartirá esa interface común. Además, el uso de funciones virtuales hace posible para la clase base definir interfaces genéricas que serán usada por todas las clases derivadas.
En este punto, usted debe preguntarse a si mismo porque una consistente interface con múltiples implementaciones es importante. La respuesta, nuevamente, no lleva a la fuerza central manejadora detrás de la programación orientada a objetos: Esta ayuda al programador a manejar programas de complejidad creciente. Por ejemplo, si usted desarrolla su programa correctamente, entonces usted sabrá que todos los objetos que usted derive de una clase base son accedidos en la misma manera general, incluso si las acciones específicas varían de una clase derivada a la próxima. Esto significa que usted necesita solo recordar una interface, en vez de varias. También, su clase derivada es libre de usar cualquiera o toda la funcionalidad provista por la clase base. No necesita reinventa esos elementos. Por tanto, la separación de interface e implementación permite la creación de librerías de clases, las cuales pueden ser provistas por un tercero. Si estas librerías son implementadas correctamente, ellas proveerán una interface común que usted puede usar para derivar clases suyas propias que cumplan sus necesidades especificasespecíficas. Por ejemplo, tanto como las Microsoft FundationFoundation Classes ( MFC ) y la librería de clases .NET Framework Windows Forms soporta programación en Windows. Usando estas clases, su programa puede heredar mucha de la funcionalidad requerida por un programa de Windows. Usted necesita agregar solo las características únicas a su aplicación. Este es un mayor beneficio cuando se programa en sistemas complejos.
 
=== UNA SIMPLE APLICACIONAPLICACIÓN DE LAS FUNCIONES VIRTUALES ===
 
Para tener una idea del poder del concepto "una interface, multiplesmúltiples
metodosmétodos", examinemos el siguiente programa. Este crea una clase base
llamada 'figura'. Esta clase almacena las dimensiones de varios
objetos de 2-dimensiones y calcula sus areasáreas. La funcionfunción 'set_dim()'
es una funcionfunción miembro estandar porque esta operacion sera comun
a las clases derivadas. Sin embargo, 'mostrar_area()' es declarada
como virtual porque el metodométodo de computar el areaárea de cada objeto
puede variar. El programa usa 'figura' para derivar dos clases
especificasespecíficas llamadas 'rectangulo' y 'triangulo'.
 
<source lang="cpp">
Línea 580:
 
 
La salida es mostrada aquiaquí:
 
<pre>
Línea 587:
</pre>
 
En el programa, notesenótese que ambas interfaces, 'rectangulo' y
'triangulo', son la misma, incluso ambas proveen sus propios metodosmétodos
para computar el areaárea de cada uno de sus objetos.
 
Dada la declaraciondeclaración para 'figura', es posible derivar una clase
llamada 'circulo' que computaracomputará el areaárea de un circulocírculo, dado en radio?
La respuesta es 'Si'. Todo lo que necesita hacer es crear un nuevo
tipo derivado que calcule el areaárea de un circulocírculo. El poder de las
funciones virtuales estaestá basado en el hecho de que usted puede
facilmentefácilmente derivar un nuevo tipo que mantendramantendrá un interface comuncomún
con otros objetos relaciones. Por ejemplo, esta es una manera de
hacerlo:
Línea 614:
</source>
 
Antes de intentar usar 'circulo', vea de cerca la definiciondefinición de
'mostrar_area()'. Note que esta usa solo el valor de x, el cual se
asume que almacena el radio. ( Recuerde, el areaárea de un circulocírculo es
calculada usando la formulafórmula 'PI*R a la 2'.) Sin embargo, la funcionfunción
'set_dim()' como se define en 'figura', asume que seranserán pasados
dos valores, no solo uno. Como 'circulocírculo' no requiere este segundo
valor, que tipo de acción podemos tomar?
 
Hay dos manera de resolver este problema. La primera y peor, usted
simplemente llama a 'set_dim()' usando un valor falso como segundo
parametroparámetro cuando use un objeto 'circulo'. Esto tiene la desventaja
de ser chapucero, en conjunto requiriendo que recuerde una excepcionexcepción
especial, la cual viola la filosofiafilosofía "una interface, muchos metodosmétodos".
 
Una mejor manera de resolver este problema es pasarle al parametroparámetro 'y'
dentro de 'set_dim()' un valor por defecto. Entonces, cuando se llame
a 'set_dim()' para un circulocírculo, necesita especificar solo el radio.
Cuando llame a 'set_dim()' para un triangulotriángulo o un rectangulorectángulo,
especificaraespecificará ambos valores. El programa expandido, el cual usa este
metodométodo, es mostrado aquiaquí:
 
 
Línea 720:
</pre>
 
TIP: Mientras que las funciones virtuales son sintacticamentesintácticamente faciles
de comprender, su verdadero poder no puede ser demostrado en ejemplos
cortos. En general, el polimorfismo logra su gran fuerza en sistemas
largos y complejos. Si continua usando C++, las oportunidades de
aplicar funciones virtuales se presentaranpresentarán por si mismas.
 
=== FUNCIONES VIRTUALES PURAS Y CLASES ABSTRACTAS ===
 
Como se ha visto, cuando una funcionfunción virtual que no es redefinida
en una clase derivada es llamada por un objeto de esa clase derivada,
la versionversión de la funcionfunción como se ha definido en la clase base es
utilizada. Sin embargo, en muchas circunstancias, no habrahabrá una
declaraciondeclaración con significado en una funcionfunción virtual dentro de la
clase base. Por ejemplo, en la clase base 'figura' usada en el ejemplo
anterior, la definiciondefinición de 'mostrar_area()' es simplemente un
sustituto sinteticosintético. No computaracomputará ni mostraramostrará el area de ningunningún
tipo de objeto. Como veraverá cuando cree sus propias libreriaslibrerías de clases
no es poco comuncomún para una funcionfunción virtual tener una definiciondefinición
sin significado en el contexto de su clase base.
 
Cuando esta situacionsituación ocurre, hay dos manera en que puede manejarla.
Una manera, como se muestra en el ejemplo, es simplemente hacer que
la funcionfunción reporte un mensaje de advertencia. Aunque esto puede ser
utilútil en ocasiones, no es el apropiado en muchas circunstancias. Por
ejemplo, puede haber funciones virtuales que simplemente deben ser
definidas por la clase derivada para que la clase derivada tenga
algunalgún significado. Considere la clase 'triangulo'. Esta no tendriatendría
significado si 'mostrar_area()' no se encuentra definida. En este
caso, usted desea algun metodo para asegurarse de que una clase
derivada, de hecho, defina todas las funciones necesarias. En
C++, la solucionsolución a este problema es la 'funcionfunción virtual pura.'
 
Una 'funcionfunción virtual pura' es una función declarada en una clase base
que no tiene definiciondefinición relativa a la base. Como resultado, cualquier
tipo derivado debe definir su propia versionversión -- esta simplemente
no puede usar la versionversión definida en la base. Para declarar una funcionfunción
virtual pura use esta forma general:
 
Línea 763:
 
 
AquiAquí, 'tipo' es el tipo de retorno de la funcionfunción y 'nombre_de_funcion'
es el nombre de la funcionfunción. Es el = 0 que designa la funcionfunción virtual
como pura. Por ejemplo, la siguiente versionversión de 'figura',
'mostrar_area()' es una funcionfunción virtual pura.
 
 
Línea 782:
</source>
 
Declarando una funcionfunción virtual como pura, se fuerza a cualquier clase
derivada a definir su propia implementación. Si una clase falla en
hacerlo, el compilador reportarareporta un error. Por ejemplo, intente
compilar esta versionversión modificando del programa de figuras, en el cual
la definición de mostrar_area() ha sido removida de la clase
'circulocírculo':
 
<source lang="cpp">
Línea 859:
</source>
 
Si una clase tiene al menos una funcionfunción virtual pura, entonces
esa clase se dice que es 'abstracta'. Una clase abstracta tiene una
caracteristicacaracterística importante: No puede haber objetos de esa clase.
En vez de eso, una clase abstracta debe ser usada solo como una base
que otras clases heredaranheredarán. La razonrazón por la cual una clase abstracta
no puede ser usada para declarar un objeto es, por supuesto, que una
o masmás de sus funciones no tienen definiciondefinición. Sin embargo, incluso
si la clase base es abstracta, la puede usar aunaún para declarar
punteros o referencias, los cuales son necesarios para soportar el
polimorfismo en tiempo de ejecucionejecución.
 
=== ENLACE TEMPRANO VS TARDIOTARDÍO ===
 
Existen dos terminostérminos que son comunmentecomúnmente usado cuando se discute
sobre programación orientada a objetos: "Enlace temprano y Enlace
TardioTardío" ( del inglesinglés "early binding and late binding" ). Relativo a
C++, estos terminostérminos se refieren a eventos que ocurren en tiempo de
compilacion y eventos que ocurren en tiempo de ejecucion,
respectivamente.
 
Enlace temprano significa que una llamada a una funcionfunción es resuelta
en tiempo de compilacioncompilación. Esto es, toda la informacioninformación necesaria para
llamar a una funcionfunción es conocida cuando el programa es compilado.
Ejemplos de enlace temprano incluyen llamadas a funciones estandarestándar,
llamadas a funciones sobrecargadas y llamadas a funciones de
operadores sobrecargados. La principal ventaja del enlace temprano es
la eficiencia -- es rapidorápido, y a menudo requiere menos memoria. Su
desventaja es falta de flexibilidad.
 
Enlace tardiotardío significa que una llamada a la funcionfunción es resuelta en
tiempo de ejecución. MasMás precisamente a que la llamada a la funcionfunción
es determinada "al vuelo" mientras el programa se ejecuta. Enlace
tardiotardío es logrado en C++ a travestravés del uso de funciones virtuales y
tipos derivados. La ventaja del enlace tardiotardío es que permite gran
flexibilidad. Puede ser usada para soportar una interface común,
mientras que se permite a varios objetos utilizar esa interface para
definir sus propias implementaciones. Por tanto, puede ser usada para
ayudar a crear libreriaslibrerías de clases, las cuales pueden ser reusadas
y extendidas. Su desventaja, sin embargo, es una ligera perdidapérdida de
velocidad de ejecucionejecución.
 
=== POLIMORFISMO Y EL PURISTA ===
 
A travestravés de este libro, y en este capitulocapítulo especificamenteespecíficamente, se ha
hecho una distinciondistinción entre polimorfismo de tiempo de ejecucionejecución y en
tiempo de compilacioncompilación. CaracteristicasCaracterísticas polimorficaspolimórficas en tiempo de
compilacion son sobrecarga de operadores y funciones. El polimorfismo
en tiempo de ejecucionejecución es logrado con funciones virtuales. La
definiciondefinición masmás comuncomún de polimorfismo es, "una interface, multiplesmúltiples
metodosmétodos", y todas estas caracteristicascaracterísticas se ajustan a este significado.
Sin embargo, aunaún existe controversia con el uso del terminotérmino
"polimorfismo".
 
Algunos puristas POO han insistido que el terminotérmino debe usarse para
referisereferirse solo a eventos que ocurren en tiempo de ejecución. También,
ellos podrianpodrían decir que solo las funciones virtuales soportan el
polimorfismo. Parte de este punto de vista estaestá fundado en el hecho
de que los primeros lenguajes de computación polimórficos fueran
intérpretes (en el que todos los eventos ocurrianocurren en tiempo de
ejecución). La llegada de lenguajes polimórficos compilador expandió
el concepto de polimorfismo. Sin embargo, aunaún algunos aseguran que el
término polimorfismo debería referirse solo a eventos en tiempo de
ejecución. La mayoría de los programadores de C++ no estanestán de acuerdo
con este punto de vista y establecen que el término aplica en ambos
casos a características en tiempo de compilación y en tiempo de
ejecución. Sin embargo, no se sorprenda si algun dia, alguien va en
contra de usted sobre el uso de este terminotérmino!
 
Si su programa usa enlace tardiotardío o temprano depende de lo que el
programa este diseñado para hacer. ( En realidad, la mayoriamayoría los
programas grandes usan una combinacioncombinación de los dos.) Enlace tardiotardío es
una de las características masmás poderosas de c++. Sin embargo, el
precio que usted paga por este poder es que su programa se ejecutaraejecutará
ligeramente masmás lento. Por lo tanto, es mejor usar enlace tardiotardío solo
cuando este le agregue significado a la estructura y manejabilidad
de su programa. ( En esencia, use -- pero no abuse -- el poder.)
Mantenga en mente, sin embargo, que la perdidapérdida de desempeño causada
por enlace tardiotardío es muy ligera, asiasí pues cuando la situación
requiera enlace tardiotardío, usted definitivamente deberiadebería usarlo.
 
</div>