C++ avanzado | Constructor virtual

¿Podemos hacer que un constructor de clases sea virtual en C++ para crear objetos polimórficos? No. Al ser C++ un lenguaje de tipo estático (el propósito de RTTI es diferente), no tiene sentido para el compilador de C++ crear un objeto polimórficamente. El compilador debe conocer el tipo de clase para crear el objeto. En otras palabras, qué tipo de objeto se creará es una decisión en tiempo de compilación desde la perspectiva del compilador de C++. Si hacemos que un constructor sea virtual, el compilador marca un error. De hecho, excepto en línea , no se permite ninguna otra palabra clave en la declaración del constructor. 

En escenarios prácticos, necesitaríamos crear un objeto de clase derivado en una jerarquía de clases basada en alguna entrada. En otras palabras, la creación de objetos y el tipo de objeto están estrechamente relacionados, lo que obliga a extender las modificaciones. El objetivo del constructor virtual es desacoplar la creación de objetos de su tipo

¿Cómo podemos crear el tipo requerido de un objeto en tiempo de ejecución? Por ejemplo, vea el siguiente programa de ejemplo: 

C

#include <iostream>
using namespace std;
 
//// LIBRARY START
class Base
{
public:
 
    Base() { }
 
    virtual // Ensures to invoke actual object destructor
    ~Base() { }
 
    // An interface
    virtual void DisplayAction() = 0;
};
 
class Derived1 : public Base
{
public:
    Derived1()
    {
        cout << "Derived1 created" << endl;
    }
 
    ~Derived1()
    {
        cout << "Derived1 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived1" << endl;
    }
};
 
class Derived2 : public Base
{
public:
    Derived2()
    {
        cout << "Derived2 created" << endl;
    }
 
    ~Derived2()
    {
        cout << "Derived2 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived2" << endl;
    }
};
 
//// LIBRARY END
 
class User
{
public:
 
    // Creates Drived1
    User() : pBase(nullptr)
    {
        // What if Derived2 is required? - Add an if-else ladder (see next sample)
        pBase = new Derived1();
    }
 
    ~User()
    {
        if( pBase )
        {
            delete pBase;
            pBase = nullptr;
        }
    }
 
    // Delegates to actual object
    void Action()
    {
        pBase->DisplayAction();
    }
 
private:
    Base *pBase;
};
 
int main()
{
    User *user = new User();
 
    // Need Derived1 functionality only
    user->Action();
 
    delete user;
}

En el ejemplo anterior, suponga que las jerarquías Base , Derived1 y Derived2 forman parte del código de la biblioteca. La clase User es una clase de utilidad que intenta hacer uso de la jerarquía. La función principal es consumir la funcionalidad de la jerarquía base a través de la clase de usuario  . 

El constructor de la clase Usuario siempre crea el objeto Derivado1 . Si el consumidor del Usuario (el principal en nuestro caso) necesita la funcionalidad Derived2 , el Usuario necesita crear “ new Derived2() “, lo que fuerza la recompilación. Recompilar es una mala forma de diseño, por lo que podemos optar por el siguiente enfoque. 

Antes de entrar en detalles, respondamos la pregunta, ¿quién dictará la creación del objeto Derived1 o Derived2 ? Claramente, es el consumidor de la clase User . La clase User puede hacer uso de una escalera if-else para crear Derived1 o Derived2 , como se muestra en el siguiente ejemplo: 

C

#include <iostream>
using namespace std;
 
//// LIBRARY START
class Base
{
public:
    Base() { }
 
    virtual // Ensures to invoke actual object destructor
    ~Base() { }
 
    // An interface
    virtual void DisplayAction() = 0;
};
 
class Derived1 : public Base
{
public:
    Derived1()
    {
        cout << "Derived1 created" << endl;
    }
 
    ~Derived1()
    {
        cout << "Derived1 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived1" << endl;
    }
};
 
class Derived2 : public Base
{
public:
    Derived2()
    {
        cout << "Derived2 created" << endl;
    }
 
    ~Derived2()
    {
        cout << "Derived2 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived2" << endl;
    }
};
 
//// LIBRARY END
 
class User
{
public:
 
    // Creates Derived1 or Derived2 based on input
    User() : pBase(nullptr)
    {
        int input; // ID to distinguish between
                   // Derived1 and Derived2
 
        cout << "Enter ID (1 or 2): ";
        cin  >> input;
 
        while( (input !=  1) && (input !=  2) )
        {
            cout << "Enter ID (1 or 2 only): ";
            cin  >> input;
        }
 
        if( input == 1 )
        {
            pBase = new Derived1;
        }
        else
        {
            pBase = new Derived2;
        }
 
        // What if Derived3 being added to the class hierarchy?
    }
 
    ~User()
    {
        if( pBase )
        {
            delete pBase;
            pBase = nullptr;
        }
    }
 
    // Delegates to actual object
    void Action()
    {
        pBase->DisplayAction();
    }
 
private:
    Base *pBase;
};
 
int main()
{
    User *user = new User();
 
    // Need either Derived1 or Derived2 functionality
    user->Action();
 
    delete user;
}

El código anterior *no* está abierto para extensión; un diseño inflexible. En palabras simples, si la biblioteca actualiza la jerarquía de la clase Base con la nueva clase Derived3 , ¿cómo puede la clase User crear el objeto Derived3 ? Una forma es actualizar la escalera if-else que crea el objeto Derived3 basado en la nueva ID de entrada 3 como se muestra a continuación: 

C

#include <iostream>
using namespace std;
 
class User
{
public:
    User() : pBase(nullptr)
    {
        // Creates Drived1 or Derived2 based on need
 
        int input; // ID to distinguish between
                   // Derived1 and Derived2
 
        cout << "Enter ID (1 or 2): ";
        cin  >> input;
 
        while( (input !=  1) && (input !=  2) )
        {
            cout << "Enter ID (1 or 2 only): ";
            cin  >> input;
        }
 
        if( input == 1 )
        {
            pBase = new Derived1;
        }
        else if( input == 2 )
        {
            pBase = new Derived2;
        }
        else
        {
            pBase = new Derived3;
        }
    }
 
    ~User()
    {
        if( pBase )
        {
            delete pBase;
            pBase = nullptr;
        }
    }
 
    // Delegates to actual object
    void Action()
    {
        pBase->DisplayAction();
    }
 
private:
    Base *pBase;
};

La modificación anterior obliga a los usuarios de la clase User a recompilar; de nuevo, ¡mal diseño (inflexible)! Además, no cerrará la clase Usuario de más modificaciones debido a la extensión Base

El problema es con la creación de objetos. La adición de una nueva clase a la jerarquía obliga a los dependientes de la clase Usuario a recompilar. ¿No podemos delegar la acción de crear objetos a la propia jerarquía de clases oa una función que se comporte virtualmente? Al delegar la creación de objetos a la jerarquía de clases (oa una función estática), podemos evitar el estrecho acoplamiento entre la jerarquía de Usuario y Base . Suficiente teoría; ver el siguiente código: 

C

#include <iostream>
using namespace std;
 
//// LIBRARY START
class Base
{
public:
 
    // The "Virtual Constructor"
    static Base *Create(int id);
 
    Base() { }
 
    virtual // Ensures to invoke actual object destructor
    ~Base() { }
 
    // An interface
    virtual void DisplayAction() = 0;
};
 
class Derived1 : public Base
{
public:
    Derived1()
    {
        cout << "Derived1 created" << endl;
    }
 
    ~Derived1()
    {
        cout << "Derived1 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived1" << endl;
    }
};
 
class Derived2 : public Base
{
public:
    Derived2()
    {
        cout << "Derived2 created" << endl;
    }
 
    ~Derived2()
    {
        cout << "Derived2 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived2" << endl;
    }
};
 
class Derived3 : public Base
{
public:
    Derived3()
    {
        cout << "Derived3 created" << endl;
    }
 
    ~Derived3()
    {
        cout << "Derived3 destroyed" << endl;
    }
 
    void DisplayAction()
    {
        cout << "Action from Derived3" << endl;
    }
};
 
// We can also declare "Create" outside Base
// But it is more relevant to limit it's scope to Base
Base *Base::Create(int id)
{
    // Just expand the if-else ladder, if new Derived class is created
    // User code need not be recompiled to create newly added class objects
 
    if( id == 1 )
    {
        return new Derived1;
    }
    else if( id == 2 )
    {
        return new Derived2;
    }
    else
    {
        return new Derived3;
    }
}
//// LIBRARY END
 
//// UTILITY START
class User
{
public:
    User() : pBase(nullptr)
    {
        // Receives an object of Base hierarchy at runtime
 
        int input;
 
        cout << "Enter ID (1, 2 or 3): ";
        cin >> input;
 
        while( (input !=  1) && (input !=  2) && (input !=  3) )
        {
            cout << "Enter ID (1, 2 or 3 only): ";
            cin >> input;
        }
 
        // Get object from the "Virtual Constructor"
        pBase = Base::Create(input);
    }
 
    ~User()
    {
        if( pBase )
        {
            delete pBase;
            pBase = nullptr;
        }
    }
 
    // Delegates to actual object
    void Action()
    {
        pBase->DisplayAction();
    }
 
private:
    Base *pBase;
};
 
//// UTILITY END
 
//// Consumer of User (UTILITY) class
int main()
{
    User *user = new User();
 
    // Action required on any of Derived objects
    user->Action();
 
    delete user;
}

La clase User es independiente de la creación de objetos. Delega esa responsabilidad a Base y proporciona una entrada en forma de ID. Si la biblioteca agrega una nueva clase Derived4 , el modificador de la biblioteca extenderá la escalera if-else dentro de Create para devolver el objeto adecuado. Los consumidores de User no necesitan volver a compilar su código debido a la extensión de Base

Tenga en cuenta que la función Create se usa para devolver diferentes tipos de objetos de clase Base en tiempo de ejecución. Actúa como un constructor virtual, también conocido como Método de fábrica en la terminología de patrones. 

El mundo de patrones demuestra diferentes formas de implementar el concepto anterior. Además, existen algunos posibles problemas de diseño con el código anterior. Nuestro objetivo es proporcionar algunas ideas sobre la construcción virtual, es decir, la creación de objetos de forma dinámica sobre la base de alguna entrada. Tenemos excelentes libros dedicados al tema. Los lectores interesados ​​pueden consultarlos para obtener más información. 

Venki . Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

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 *