Рубрики

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

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

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

Как мы можем создать требуемый тип объекта во время выполнения? Например, см. Следующий пример программы.

#include <iostream>

using namespace std;

  
//// БИБЛИОТЕЧНЫЙ СТАРТ

class Base

{

public:

  

    Base() { }

  

    virtual // Гарантирует вызов фактического деструктора объекта

    ~Base() { }

  

    // Интерфейс

    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 User

{

public:

  

    // Создаем Drived1

    User() : pBase(0)

    {

        // Что делать, если требуется Derived2? - Добавьте лестницу if-else (см. Следующий пример)

        pBase = new Derived1();

    }

  

    ~User()

    {

        if( pBase )

        {

            delete pBase;

            pBase = 0;

        }

    }

  

    // Делегаты для реального объекта

    void Action()

    {

        pBase->DisplayAction();

    }

  

private:

    Base *pBase;

};

  

int main()

{

    User *user = new User();

  

    // Нужна только функциональность Derived1

    user->Action();

  

    delete user;

}

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

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

Прежде чем углубляться в детали, давайте ответим, кто будет диктовать создание объекта Derived1 или Derived2 ? Понятно, что это потребитель класса User . Класс User может использовать лестницу if-else для создания Derived1 или Derived2 , как показано в следующем примере:

#include <iostream>

using namespace std;

  
//// БИБЛИОТЕЧНЫЙ СТАРТ

class Base

{

public:

    Base() { }

  

    virtual // Гарантирует вызов фактического деструктора объекта

    ~Base() { }

  

    // Интерфейс

    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 User

{

public:

  

    // Создает Derived1 или Derived2 на основе ввода

    User() : pBase(0)

    {

        int input; // ID для различения

                   // Derived1 и 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;

        }

  

        // Что, если Derived3 добавляется в иерархию классов?

    }

  

    ~User()

    {

        if( pBase )

        {

            delete pBase;

            pBase = 0;

        }

    }

  

    // Делегаты для реального объекта

    void Action()

    {

        pBase->DisplayAction();

    }

  

private:

    Base *pBase;

};

  

int main()

{

    User *user = new User();

  

    // Нужна функциональность Derived1 или Derived2

    user->Action();

  

    delete user;

}

Приведенный выше код * не * открыт для расширения, негибкий дизайн. Проще говоря, если библиотека обновляет иерархию Базового класса новым классом Derived3 . Как класс User может создать объект Derived3 ? Один из способов — обновить лестницу if-else, которая создает объект Derived3 на основе нового входного идентификатора 3, как показано ниже,

#include <iostream>

using namespace std;

  

class User

{

public:

    User() : pBase(0)

    {

        // Создаем Drived1 или Derived2 в зависимости от необходимости

  

        int input; // ID для различения

                   // Derived1 и 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 = 0;

        }

    }

  

    // Делегаты для реального объекта

    void Action()

    {

        pBase->DisplayAction();

    }

  

private:

    Base *pBase;

};

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

Проблема с созданием объектов. Добавление нового класса в иерархию, заставляющее иждивенцев класса User перекомпилироваться. Разве мы не можем делегировать действие создания объектов самой иерархии классов или функции, которая ведет себя виртуально? Передавая создание объекта иерархии классов (или статической функции), мы можем избежать тесной связи между пользовательской и базовой иерархией. Достаточно теории, см. Следующий код,

#include <iostream>

using namespace std;

  
//// БИБЛИОТЕЧНЫЙ СТАРТ

class Base

{

public:

  

    // Виртуальный конструктор

    static Base *Create(int id);

  

    Base() { }

  

    virtual // Гарантирует вызов фактического деструктора объекта

    ~Base() { }

  

    // Интерфейс

    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;

    }

};

  
// Мы также можем объявить «Create» вне Base
// Но более уместно ограничить его область действия Base

Base *Base::Create(int id)

{

    // Просто расширяем лестницу if-else, если создан новый производный класс

    // Код пользователя не нужно перекомпилировать для создания вновь добавленных объектов класса

  

    if( id == 1 )

    {

        return new Derived1;

    }

    else if( id == 2 )

    {

        return new Derived2;

    }

    else

    {

        return new Derived3;

    }

}
//// КОНЕЦ БИБЛИОТЕКИ

  
//// КОММУНАЛЬНЫЙ СТАРТ

class User

{

public:

    User() : pBase(0)

    {

        // Получает объект базовой иерархии во время выполнения

  

        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;

        }

  

        // Получить объект из «Виртуального Конструктора»

        pBase = Base::Create(input);

    }

  

    ~User()

    {

        if( pBase )

        {

            delete pBase;

            pBase = 0;

        }

    }

  

    // Делегаты для реального объекта

    void Action()

    {

        pBase->DisplayAction();

    }

  

private:

    Base *pBase;

};

  
//// конец утилиты

  
//// Потребительский класс пользователя (UTILITY)

int main()

{

    User *user = new User();

  

    // Требуемое действие для любого из производных объектов

    user->Action();

  

    delete user;

}

Класс User не зависит от создания объекта. Он передает эту ответственность Base и предоставляет информацию в форме идентификатора. Если библиотека добавляет новый класс Derived4 , модификатор библиотеки расширит лестницу if-else внутри Create, чтобы вернуть соответствующий объект. Потребители Пользователя не должны перекомпилировать свой код из-за расширения Base .

Обратите внимание, что функция Create используется для возврата различных типов объектов базового класса во время выполнения. Он действует как виртуальный конструктор, также называемый фабричным методом в шаблонной терминологии.

Образцы мира демонстрируют разные способы реализации вышеуказанной концепции. Также есть некоторые потенциальные проблемы дизайна с приведенным выше кодом. Наша цель — дать некоторое представление о виртуальном построении, динамически создавая объекты на основе некоторого ввода. У нас есть отличные книги, посвященные этой теме, заинтересованные читатели могут обратиться к ним за дополнительной информацией.

Венки . Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.

Рекомендуемые посты:

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

0.00 (0%) 0 votes