Продвинутый C ++ | Виртуальный конструктор

Опубликовано: 7 Января, 2022

Можем ли мы сделать конструктор класса виртуальным в C ++ для создания полиморфных объектов? Нет. C ++ является статически типизированным языком (назначение RTTI другое), поэтому для компилятора C ++ бессмысленно создавать объект полиморфно. Компилятор должен знать тип класса для создания объекта. Другими словами, тип создаваемого объекта - это решение во время компиляции с точки зрения компилятора C ++. Если мы сделаем конструктор виртуальным, компилятор помечает ошибку. Фактически, кроме inline , в объявлении конструктора не допускается никаких других ключевых слов.

В практических сценариях нам потребуется создать объект производного класса в иерархии классов на основе некоторого ввода. Другими словами, создание объекта и тип объекта тесно связаны, что приводит к расширению модификаций. Цель виртуального конструктора - отделить создание объекта от его типа .

How can we create the required type of an object at runtime? For example, see the following sample program.

#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;
}

В приведенном выше примере предположим, что иерархии Base , Derived1 и Derived2 являются частью кода библиотеки. Класс User - это служебный класс, пытающийся использовать иерархию. Основная функция использует функциональность базовой иерархии через класс User.

Конструктор класса User всегда создает объект Derived1. Если потребитель Абонентский (главный в нашем случае) необходима функциональность Derived2, потребности пользователей , чтобы создать „новый Derived2 ()“ , и это заставляет перекомпиляцию. Перекомпиляция - плохой способ проектирования, поэтому мы можем выбрать следующий подход.

Before going into details, let us answer, who will dictate to create either of Derived1 or Derived2 object? Clearly, it is the consumer of User class. The User class can make use of if-else ladder to create either Derived1 or Derived2, as shown in the following sample,

#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;
}

The above code is *not* open for extension, an inflexible design. In simple words, if the library updates the Base class hierarchy with new class Derived3. How can the User class creates Derived3 object? One way is to update the if-else ladder that creates Derived3 object based on new input ID 3 as shown below,

#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;
};

Вышеупомянутая модификация вынуждает пользователей класса User перекомпилировать плохой (негибкий) дизайн! И не закроет класс User от дальнейших изменений из-за расширения Base.

The problem is with the creation of objects. Addition of new class to the hierarchy forcing dependents of User class to recompile. Can’t we delegate the action of creating objects to class hierarchy itself or to a function that behaves virtually? By delegating the object creation to class hierarchy (or to a static function) we can avoid the tight coupling between User and Base hierarchy. Enough theory, see the following code,

#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 *
C++ C