En la programación orientada a objetos , en lenguajes como C ++ y Object Pascal , una función virtual o un método virtual es una función o método heredable y anulable para el que se facilita el envío dinámico . Este concepto es una parte importante de la parte del polimorfismo (en tiempo de ejecución) de la programación orientada a objetos (OOP). En resumen, una función virtual define una función de destino que se ejecutará, pero es posible que el destino no se conozca en el momento de la compilación.
La mayoría de los lenguajes de programación, como Java , PHP y Python , tratan todos los métodos como virtuales por defecto [1] [2] y no proporcionan un modificador para cambiar este comportamiento. Sin embargo, algunos lenguajes proporcionan modificadores para evitar que los métodos sean anulados por clases derivadas (como la palabra clave final en Java [3] y PHP [4] ).
Propósito
El concepto de función virtual resuelve el siguiente problema:
En la programación orientada a objetos, cuando una clase derivada hereda de una clase base, se puede hacer referencia a un objeto de la clase derivada mediante un puntero o referencia del tipo de clase base en lugar del tipo de clase derivada. Si hay métodos de clase base anulados por la clase derivada, el método realmente llamado por dicha referencia o puntero puede enlazarse 'temprano' (por el compilador), de acuerdo con el tipo declarado de puntero o referencia, o 'tardío' (es decir, por el sistema de tiempo de ejecución del lenguaje), de acuerdo con el tipo real del objeto al que se hace referencia.
Las funciones virtuales se resuelven 'tarde'. Si la función en cuestión es 'virtual' en la clase base, la implementación de la función de la clase más derivada se llama de acuerdo con el tipo real del objeto al que se hace referencia, independientemente del tipo declarado de puntero o referencia. Si no es 'virtual', el método se resuelve 'temprano' y la función llamada se selecciona de acuerdo con el tipo declarado de puntero o referencia.
Las funciones virtuales permiten que un programa llame a métodos que ni siquiera existen necesariamente en el momento en que se compila el código.
En C ++, los métodos virtuales se declaran anteponiendo la virtual
palabra clave a la declaración de la función en la clase base. Este modificador es heredado por todas las implementaciones de ese método en clases derivadas, lo que significa que pueden continuar reemplazándose entre sí y enlazarse tarde. E incluso si los métodos propiedad de la clase base llaman al método virtual, en su lugar llamarán al método derivado. La sobrecarga ocurre cuando dos o más métodos en una clase tienen el mismo nombre de método pero diferentes parámetros. Anular significa tener dos métodos con el mismo nombre de método y parámetros. La sobrecarga también se conoce como coincidencia de funciones y la anulación como asignación de funciones dinámicas.
Ejemplo
Por ejemplo, una clase base Animal
podría tener una función virtual Eat
. La subclase Llama
se implementaría de manera Eat
diferente a la subclase Wolf
, pero se puede invocar Eat
en cualquier instancia de clase denominada Animal y obtener el Eat
comportamiento de la subclase específica.
class Animal { public : // Intencionalmente no virtual: void Move ( void ) { std :: cout << "Este animal se mueve de alguna manera" << std :: endl ; } virtual void Eat ( void ) = 0 ; };// La clase "Animal" puede tener una definición de Comer si se desea. class Llama : public Animal { public : // La función no virtual Move se hereda pero no se anula. void Eat ( void ) override { std :: cout << "¡Las llamas comen hierba!" << std :: endl ; } };
Esto permite a un programador procesar una lista de objetos de clase Animal
, diciéndoles a cada uno que coma (llamando Eat
), sin necesidad de saber qué tipo de animal puede estar en la lista, cómo come cada animal o cuál es el conjunto completo de posibles los tipos de animales pueden serlo.
Podemos ver mejor cómo funcionan las funciones virtuales implementando el ejemplo anterior en C
#include / * un objeto apunta a su clase ... * / struct Animal { const struct AnimalClass * class ; };/ * que contiene la función virtual Animal.Eat * / struct AnimalClass { void ( * Eat ) ( struct Animal * ); // función 'virtual' };/ * Dado que Animal.Move no es una función virtual, no está en la estructura anterior. * / void Move ( struct Animal * self ) { printf ( " movido de alguna manera \ n " , ( void * ) self ); }/ * a diferencia de Move, que ejecuta Animal.Move directamente, Eat no puede saber a qué función (si corresponde) llamar en tiempo de compilación. Animal.Eat solo se puede resolver en tiempo de ejecución cuando se llama a Eat. * / void Eat ( struct Animal * self ) { const struct AnimalClass * class = * ( const void ** ) self ; if ( clase -> Comer ) clase -> Comer ( uno mismo ); // ejecuta Animal.Eat else fprintf ( stderr , "Eat no implementado \ n " ); }/ * implementación de Llama.Eat Esta es la función de destino a ser llamada por 'void Eat (struct Animal *)'. * / static void _Llama_eat ( struct Animal * self ) { printf ( " ¡Las llamas comen pasto! \ n " , ( void * ) self ); }/ * inicializar clase * / const struct AnimalClass Animal = {( void * ) 0 }; // la clase base no implementa Animal.Eat const struct AnimalClass Llama = { _Llama_eat }; // pero la clase derivada lo haceint main ( void ) { / * objetos init como instancia de su clase * / struct Animal animal = { & Animal }; struct Animal llama = { & Llama }; Mover ( & animal ); // Animal.Move Move ( & llama ); // Llama.Move Eat ( & animal ); // no se puede resolver Animal.Eat, así que imprime "No implementado" en stderr Eat ( & llama ); // resuelve Llama.Eat y ejecuta }
Clases abstractas y funciones virtuales puras
Una función virtual pura o un método virtual puro es una función virtual que debe implementar una clase derivada si la clase derivada no es abstracta . Las clases que contienen métodos virtuales puros se denominan "abstractas" y no se pueden instanciar directamente. Una subclase de una clase abstracta solo se puede instanciar directamente si todos los métodos virtuales puros heredados han sido implementados por esa clase o una clase principal. Los métodos virtuales puros suelen tener una declaración ( firma ) y ninguna definición ( implementación ).
Como ejemplo, una clase base abstracta MathSymbol
puede proporcionar una función virtual pura doOperation()
y clases derivadas Plus
e Minus
implementar doOperation()
para proporcionar implementaciones concretas. La implementación doOperation()
no tendría sentido en la MathSymbol
clase, ya que MathSymbol
es un concepto abstracto cuyo comportamiento se define únicamente para cada tipo (subclase) de MathSymbol
. De manera similar, una subclase dada de MathSymbol
no estaría completa sin una implementación de doOperation()
.
Aunque los métodos virtuales puros generalmente no tienen implementación en la clase que los declara, los métodos virtuales puros en algunos lenguajes (por ejemplo, C ++ y Python) pueden contener una implementación en su clase de declaración, proporcionando un comportamiento alternativo o predeterminado que una clase derivada puede delegar. , si es apropiado. [5] [6]
Las funciones virtuales puras también se pueden usar cuando las declaraciones de métodos se usan para definir una interfaz , similar a lo que especifica explícitamente la palabra clave de interfaz en Java. En tal uso, las clases derivadas proporcionarán todas las implementaciones. En tal patrón de diseño , la clase abstracta que sirve como interfaz contendrá solo funciones virtuales puras, pero no miembros de datos ni métodos ordinarios. En C ++, el uso de clases puramente abstractas como interfaces funciona porque C ++ admite herencia múltiple . Sin embargo, debido a que muchos lenguajes de programación orientada a objetos no admiten la herencia múltiple, a menudo proporcionan un mecanismo de interfaz independiente. Un ejemplo es el lenguaje de programación Java .
Comportamiento durante la construcción y destrucción
Los lenguajes difieren en su comportamiento mientras se ejecuta el constructor o destructor de un objeto. Por esta razón, generalmente se desaconseja llamar a funciones virtuales en constructores.
En C ++, se llama a la función "base". Específicamente, se llama a la función más derivada que no es más derivada que la clase del constructor actual. [7] Si esa función es una función virtual pura, entonces ocurre un comportamiento indefinido . [8] Esto es cierto incluso si la clase contiene una implementación para esa función virtual pura. No se requiere una implementación de C ++ conforme (y generalmente no es posible) para detectar llamadas indirectas a funciones virtuales puras en tiempo de compilación o tiempo de enlace . Algunos sistemas en tiempo de ejecución emitirán un error de llamada de función virtual pura cuando encuentren una llamada a una función virtual pura en tiempo de ejecución .
En Java y C #, se llama a la implementación derivada, pero el constructor derivado aún no inicializa algunos campos (aunque se inicializan a sus valores cero predeterminados). [9] Algunos patrones de diseño , como Abstract Factory Pattern , promueven activamente este uso en lenguajes que admiten esta capacidad.
Destructores virtuales
Los lenguajes orientados a objetos generalmente administran la asignación y desasignación de memoria automáticamente cuando se crean y destruyen objetos. Sin embargo, algunos lenguajes orientados a objetos permiten implementar un método destructor personalizado, si se desea. Si el lenguaje en cuestión utiliza la administración automática de memoria, el destructor personalizado (generalmente llamado finalizador en este contexto) que se llama seguramente será el apropiado para el objeto en cuestión. Por ejemplo, si se crea un objeto de tipo Wolf que hereda Animal, y ambos tienen destructores personalizados, el llamado será el declarado en Wolf.
En contextos de administración de memoria manual, la situación puede ser más compleja, particularmente en relación con el envío estático . Si un objeto de tipo Wolf es creado pero apuntado por un puntero Animal, y es este tipo de puntero Animal el que se elimina, el destructor llamado puede ser el definido para Animal y no el de Wolf, a menos que el destructor sea virtual. . Este es particularmente el caso de C ++, donde el comportamiento es una fuente común de errores de programación si los destructores no son virtuales.
Ver también
- Método abstracto
- Herencia
- Superclase
- Herencia virtual
- Interfaz (programación orientada a objetos)
- Modelo de objeto componente
- Tabla de métodos virtuales
Referencias
- ^ "Polimorfismo (los tutoriales de Java ™> Aprendizaje del lenguaje Java> Interfaces y herencia)" . docs.oracle.com . Consultado el 11 de julio de 2020 .
- ^ "9. Clases - Documentación de Python 3.9.2" . docs.python.org . Consultado el 23 de febrero de 2021 .
- ^ "Redacción de clases y métodos finales (Tutoriales de Java ™> Aprendizaje del lenguaje Java> Interfaces y herencia)" . docs.oracle.com . Consultado el 11 de julio de 2020 .
- ^ "PHP: Palabra clave final - Manual" . www.php.net . Consultado el 11 de julio de 2020 .
- ^ Destructores virtuales puros - cppreference.com
- ^ "abc - Clases base abstractas: @ abc.abstractmethod"
- ^ Meyers, Scott (6 de junio de 2005). "Nunca llame a funciones virtuales durante la construcción o la destrucción" .
- ^ "N4659: Borrador de trabajo, estándar para lenguaje de programación C ++" (PDF) . §13.4.
- ^ Ganesh, SG (1 de agosto de 2011). "Joy of Programming: Calling Virtual Functions from Constructors" .