stringtranslate.com

función virtual

En la programación orientada a objetos, como la que se usa a menudo en C++ y Object Pascal , una función virtual o un método virtual es una función o método heredable y anulable que se distribuye dinámicamente . Las funciones virtuales son una parte importante del polimorfismo (en tiempo de ejecución) en la programación orientada a objetos (OOP). Permiten la ejecución de funciones de destino que no se identificaron con precisión en el momento de la compilación.

La mayoría de los lenguajes de programación, como JavaScript , PHP y Python , tratan todos los métodos como virtuales de forma predeterminada [1] [2] y no proporcionan un modificador para cambiar este comportamiento. Sin embargo, algunos lenguajes proporcionan modificadores para evitar que las clases derivadas anulen los métodos (como las palabras clave final y privada en Java [3] y PHP [4] ).

Objetivo

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 vincularse (vincularse) ya sea "temprano" (por el compilador), de acuerdo con el tipo declarado de puntero o referencia, o "tarde" (es decir, por el sistema de ejecución del lenguaje), según 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 en la clase más derivada se llama de acuerdo con el tipo real del objeto al que se hace referencia, independientemente del tipo declarado del puntero o referencia. Si no es "virtual", el método se resuelve "temprano" y se selecciona según el tipo declarado de puntero o referencia.

Las funciones virtuales permiten que un programa llame a métodos que ni siquiera necesariamente existen en el momento en que se compila el código. [ cita necesaria ]

En C++, los métodos virtuales se declaran anteponiendo la virtualpalabra clave a la declaración de la función en la clase base. Este modificador lo heredan todas las implementaciones de ese método en clases derivadas, lo que significa que pueden continuar anulándose entre sí y vincularse en tiempo de ejecución. 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 parámetros diferentes. 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 mapeo de funciones dinámicas.

Ejemplo

C++

Diagrama de clases de animales

Por ejemplo, una clase base Animalpodría tener una función virtual Eat. La subclase Llamase implementaría Eatde manera diferente a la subclase Wolf, pero se puede invocar Eaten cualquier instancia de clase denominada Animal y obtener el Eatcomportamiento de la subclase específica.

class Animal { public : // Intencionalmente no virtual: void Move () { std :: cout << "Este animal se mueve de alguna manera" << std :: endl ; } vacío virtual Comer () = 0 ; };                  // La clase "Animal" puede poseer una definición para Comer si se desea. class Llama : public Animal { public : // La función no virtual Move se hereda pero no se anula. void Eat () 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 a Eat), sin necesidad de saber qué tipo de animal puede haber en la lista, cómo come cada animal o cuál es el conjunto completo de objetos posibles. tipos de animales podrían ser.

En C, el mecanismo detrás de las funciones virtuales podría proporcionarse de la siguiente manera:

#incluir <stdio.h> /* 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 ( "<Animal en %p> se movió de alguna manera \n " , ( void * ) self ); }        /* a diferencia de Move, que ejecuta Animal.Move directamente,  Eat no puede saber qué función (si corresponde) llamar en el momento de la compilación.  Animal.Eat solo se puede resolver en tiempo de ejecución cuando se llama a Eat. */ void Comer ( struct Animal * self ) { const struct AnimalClass * class = * ( const void ** ) self ; si ( clase -> Comer ) clase -> Comer ( yo ); // ejecutar Animal.Eat else fprintf ( stderr , "Comer no implementado \n " ); }                      /* implementación de Llama.Eat esta es la función objetivo  que será llamada por 'void Eat(struct Animal *).' */ static void _Llama_eat ( struct Animal * self ) { printf ( "<Llama en %p> ¡Las llamas comen hierba! \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 sí              int main ( void ) { /* objetos init como instancia de su clase */ struct Animal animal = { & Animal }; estructura 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 ser implementada por 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 crear instancias de ellas directamente. Solo se puede crear una instancia directa de una subclase de una clase abstracta si esa clase o una clase principal han implementado todos los métodos virtuales puros heredados. Los métodos virtuales puros suelen tener una declaración ( firma ) y ninguna definición ( implementación ).

Como ejemplo, una clase base abstracta MathSymbolpuede proporcionar una función virtual pura doOperation()y clases derivadas Pluse Minusimplementar doOperation()para proporcionar implementaciones concretas. La implementación doOperation()no tendría sentido en la MathSymbolclase, ya que lo MathSymboles un concepto abstracto cuyo comportamiento se define únicamente para cada tipo (subclase) de MathSymbol. De manera similar, una subclase determinada de MathSymbolno estaría completa sin una implementación de doOperation().

Aunque los métodos virtuales puros normalmente no tienen implementación en la clase que los declara, a los métodos virtuales puros en algunos lenguajes (por ejemplo, C++ y Python) se les permite contener una implementación en su clase declarante, proporcionando un comportamiento alternativo o predeterminado que una clase derivada puede delegar en , si es apropiado. [5] [6]

Las funciones virtuales puras también se pueden utilizar cuando las declaraciones de métodos se utilizan para definir una interfaz , similar a lo que especifica explícitamente la palabra clave de interfaz en Java. En tal uso, las clases derivadas suministrarán todas las implementaciones. En tal patrón de diseño , la clase abstracta que sirve como interfaz contendrá sólo 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 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 no se recomienda llamar a funciones virtuales en los 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 o destructor actual. [7] : §15.7.3  [8] [9] Si esa función es una función virtual pura, entonces se produce un comportamiento indefinido . [7] : §13.4.6  [8] Esto es cierto incluso si la clase contiene una implementación para esa función virtual pura, ya que una llamada a una función virtual pura debe calificarse explícitamente. [10] No se requiere (y generalmente no es capaz) una implementación conforme de C++ para detectar llamadas indirectas a funciones virtuales puras en tiempo de compilación o de enlace . Algunos sistemas en tiempo de ejecución emitirán un error de llamada a 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 sí se inicializan con sus valores cero predeterminados). [11] Algunos patrones de diseño , como el patrón Abstract Factory , promueven activamente este uso en lenguajes que soportan esta capacidad.

Destructores virtuales

Los lenguajes orientados a objetos suelen gestionar 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 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 gestión manual de memoria, la situación puede ser más compleja, particularmente en relación con el envío estático . Si se crea un objeto de tipo Lobo pero al que apunta un puntero Animal, y es este tipo de puntero Animal el que se elimina, el destructor llamado puede ser en realidad el definido para Animal y no el de Lobo, 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

Referencias

  1. ^ "Polimorfismo (Tutoriales de Java ™> Aprendizaje del lenguaje Java> Interfaces y herencia)". docs.oracle.com . Consultado el 11 de julio de 2020 .
  2. ^ "9. Clases: documentación de Python 3.9.2". docs.python.org . Consultado el 23 de febrero de 2021 .
  3. ^ "Escritura de métodos y clases finales (Tutoriales de Java ™> Aprendizaje del lenguaje Java> Interfaces y herencia)". docs.oracle.com . Consultado el 11 de julio de 2020 .
  4. ^ "PHP: Palabra clave final - Manual". www.php.net . Consultado el 11 de julio de 2020 .
  5. ^ Destructores virtuales puros - cppreference.com
  6. ^ "abc - Clases base abstractas: @abc.abstractmethod"
  7. ^ ab "N4659: Borrador de trabajo, estándar para el lenguaje de programación C++" (PDF) .
  8. ^ ab Chen, Raymond (28 de abril de 2004). "¿Qué es __purecall?".
  9. ^ Meyers, Scott (6 de junio de 2005). "Nunca llame a funciones virtuales durante la construcción o destrucción".
  10. ^ Chen, Raymond (11 de octubre de 2013). "Caso de esquina de C++: puede implementar funciones virtuales puras en la clase base".
  11. ^ Ganesh, SG (1 de agosto de 2011). "El placer de programar: llamar a funciones virtuales desde constructores".