Funciones virtuales y polimorfismo en tiempo de ejecución en C++

Una función virtual es una función miembro que se declara en la clase base mediante la palabra clave virtual y se redefine (anulada) en la clase derivada . Le dice al compilador que realice un enlace tardío donde el compilador hace coincidir el objeto con la función llamada correcta y lo ejecuta durante el tiempo de ejecución. Esta técnica de cae bajo el polimorfismo de tiempo de ejecución.

El término polimorfismo significa la capacidad de tomar muchas formas. Ocurre si hay una jerarquía de clases que están todas relacionadas entre sí por herencia . En palabras simples, cuando desglosamos el polimorfismo en ‘Poly – Many’ y ‘morphism – Forms’ , significa mostrar diferentes características en diferentes situaciones. 

Polymorphism

Jerarquía de clases

Nota : en C++, lo que significa llamar a funciones virtuales es eso; si llamamos a una función miembro, podría hacer que se ejecute una función diferente dependiendo del tipo de objeto que la invoque. 
Debido a que la anulación de clases derivadas aún no se ha realizado, el mecanismo de llamada virtual no está permitido en los constructores. También mencionar que los objetos se construyen desde cero o siguen un enfoque de abajo hacia arriba.

 

Considere el siguiente programa simple como un ejemplo de polimorfismo en tiempo de ejecución . Lo principal a tener en cuenta sobre el programa es que la función de la clase derivada se llama utilizando un puntero de clase base.
La idea es que las funciones virtuales se llamen según el tipo de instancia de objeto al que se apunta o se hace referencia, no según el tipo de puntero o referencia.
En otras palabras, las funciones virtuales se resuelven tarde, en tiempo de ejecución.

Ahora, veremos un ejemplo sin usar los conceptos de función virtual para aclarar su comprensión.

C++

// C++ program to demonstrate how we will calculate
// area of shapes without virtual function
#include <iostream>
using namespace std;
 
// Base class
class Shape {
public:
    // parameterized constructor
    Shape(int l, int w)
    {
        length = l;
        width = w;
    }
    int get_Area()
    {
        cout << "This is call to parent class area\n";
        // Returning 1 in user-defined function means true
        return 1;
    }
 
protected:
    int length, width;
};
 
// Derived class
class Square : public Shape {
public:
    Square(int l = 0, int w = 0)
        : Shape(l, w)
    {
    } // declaring and initializing derived class
    // constructor
    int get_Area()
    {
        cout << "Square area: " << length * width << '\n';
        return (length * width);
    }
};
// Derived class
class Rectangle : public Shape {
public:
    Rectangle(int l = 0, int w = 0)
        : Shape(l, w)
    {
    } // declaring and initializing derived class
    // constructor
    int get_Area()
    {
        cout << "Rectangle area: " << length * width
             << '\n';
        return (length * width);
    }
};
 
int main()
{
    Shape* s;
 
    // Making object of child class Square
    Square sq(5, 5);
 
    // Making object of child class Rectangle
    Rectangle rec(4, 5);
    s = &sq; // reference variable
    s->get_Area();
    s = &rec; // reference variable
    s->get_Area();
 
    return 0; // too tell the program executed
    // successfully
}
Producción

This is call to parent class area
This is call to parent class area

En el ejemplo anterior:

  • Almacenamos la dirección del objeto Rectángulo y Cuadrado de la clase de cada niño en s y
  • Luego llamamos a la función get_Area() en él,
  • Idealmente, debería haber llamado a las respectivas funciones get_Area() de las clases secundarias, pero
  • En su lugar, llama al get_Area() definido en la clase base.
  • Esto sucede debido a un enlace estático, lo que significa que el compilador que está en la clase base establece la llamada a get_Area() solo una vez.

Ejemplo: programa en C++ para calcular el área de formas usando la función virtual

C++

// C++ program to demonstrate how we will calculate
// the area of shapes USING VIRTUAL FUNCTION
#include <fstream>
#include <iostream>
using namespace std;
 
// Declaration of Base class
class Shape {
public:
    // Usage of virtual constructor
    virtual void calculate()
    {
        cout << "Area of your Shape ";
    }
    // usage of virtual Destuctor to avoid memory leak
    virtual ~Shape()
    {
        cout << "Shape Destuctor Call\n";
    }
};
 
// Declaration of Derived class
class Rectangle : public Shape {
public:
    int width, height, area;
 
    void calculate()
    {
        cout << "Enter Width of Rectangle: ";
        cin >> width;
 
        cout << "Enter Height of Rectangle: ";
        cin >> height;
 
        area = height * width;
        cout << "Area of Rectangle: " << area << "\n";
    }
 
    // Virtual Destuctor for every Derived class
    virtual ~Rectangle()
    {
        cout << "Rectangle Destuctor Call\n";
    }
};
 
// Declaration of 2nd derived class
class Square : public Shape {
public:
    int side, area;
 
    void calculate()
    {
        cout << "Enter one side your of Square: ";
        cin >> side;
 
        area = side * side;
        cout << "Area of Square: " << area << "\n";
    }
   
   // Virtual Destuctor for every Derived class
    virtual ~Square()
    {
        cout << "Square Destuctor Call\n";
    }
};
 
int main()
{
 
    // base class pointer
    Shape* S;
    Rectangle r;
 
    // initialization of reference variable
    S = &r;
 
    // calling of Rectangle function
    S->calculate();
    Square sq;
 
    // initialization of reference variable
    S = &sq;
 
    // calling of Square function
    S->calculate();
 
    // return 0 to tell the program executed
    // successfully
    return 0;
}

Producción:

Enter Width of Rectangle: 10
Enter Height of Rectangle: 20
Area of Rectangle: 200
Enter one side your of Square: 16
Area of Square: 256

¿Cuál es el uso?  
Las funciones virtuales nos permiten crear una lista de punteros de clase base y métodos de llamada de cualquiera de las clases derivadas sin siquiera saber el tipo de objeto de clase derivada. 

Ejemplo de la vida real para comprender la implementación de la función virtual

Considere el software de gestión de empleados para una organización.
Deje que el código tenga una clase base simple Employee , la clase contiene funciones virtuales como aumentar el salario() , transferir() , promover() , etc. Diferentes tipos de empleados como Gerentes , Ingenieros , etc., pueden tener sus propias implementaciones de la virtual funciones presentes en la clase base Employee

En nuestro software completo, solo necesitamos pasar una lista de empleados en todas partes y llamar a las funciones apropiadas sin siquiera saber el tipo de empleado. Por ejemplo, podemos aumentar fácilmente el salario de todos los empleados iterando a través de la lista de empleados. Cada tipo de empleado puede tener su propia lógica en su clase, pero no tenemos que preocuparnos porque si aumentar el salario() está presente para un tipo de empleado específico, solo se llamaría a esa función.

CPP

// C++ program to demonstrate how a virtual function
// is used in a real life scenario
 
class Employee {
public:
    virtual void raiseSalary()
    {
        // common raise salary code
    }
 
    virtual void promote()
    {
        // common promote code
    }
};
 
class Manager : public Employee {
    virtual void raiseSalary()
    {
        // Manager specific raise salary code, may contain
        // increment of manager specific incentives
    }
 
    virtual void promote()
    {
        // Manager specific promote
    }
};
 
// Similarly, there may be other types of employees
 
// We need a very simple function
// to increment the salary of all employees
// Note that emp[] is an array of pointers
// and actual pointed objects can
// be any type of employees.
// This function should ideally
// be in a class like Organization,
// we have made it global to keep things simple
void globalRaiseSalary(Employee* emp[], int n)
{
    for (int i = 0; i < n; i++) {
        // Polymorphic Call: Calls raiseSalary()
        // according to the actual object, not
        // according to the type of pointer
        emp[i]->raiseSalary();
    }
}

Al igual que la función ‘ globalRaiseSalary() , puede haber muchas otras operaciones que se pueden realizar en una lista de empleados sin siquiera conocer el tipo de instancia del objeto. 
Las funciones virtuales son tan útiles que lenguajes posteriores como Java mantienen todos los métodos virtuales por defecto .

¿Cómo realiza el compilador la resolución en tiempo de ejecución?

El compilador mantiene dos cosas para cumplir este propósito:

  • vtable: una tabla de punteros de función, mantenida por clase. 
  • vptr: un puntero a vtable, mantenido por instancia de objeto (consulteestopara ver un ejemplo).
     

compiler perform runtime resolution

El compilador agrega código adicional en dos lugares para mantener y usar vptr .

1. Código en cada constructor. Este código establece el vptr del objeto que se está creando. Este código configura vptr para que apunte a la vtable de la clase. 

2. Código con llamada de función polimórfica (por ejemplo, bp->show() en el código anterior). Siempre que se realiza una llamada polimórfica, el compilador inserta código para buscar primero vptr utilizando un puntero o referencia de clase base (en el ejemplo anterior, dado que el objeto apuntado o referido es de un tipo derivado, se accede a vptr de una clase derivada). Una vez que se obtiene vptr , se puede acceder a vtable de la clase derivada. Usando vtable , se accede y se llama a la dirección de la función de clase derivada show() .

¿Es esta una forma estándar de implementación del polimorfismo en tiempo de ejecución en C++?  
Los estándares de C++ no exigen exactamente cómo se debe implementar el polimorfismo en tiempo de ejecución, pero los compiladores generalmente usan variaciones menores en el mismo modelo básico.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *