Проблема с наследованием абстрактного класса и конструктором в C ++ - PullRequest
0 голосов
/ 13 октября 2019
class Base {
public:
    Base(int a) : a_(a) {
        //do something
        someMethod();
        //do something else
    };
protected:
    int a_;
    virtual void someMethod() = 0 {};
};

class Derived : Base {
public:
    Derived() {
        Base::Base(42);
    }
protected:
    void someMethod() override {
        //realisation
    }
};

int main() {
    Derived *obj = new Derived();
    delete obj;
}

Этот код не работает из-за двух ошибок: необходим конструктор базового класса по умолчанию и конструктор базового класса с параметрами не может быть вызван из-за использования абстрактных методов

Моя проблема в том, что someMethod() реализовано в class Derived вообще не вызывается, когда я создаю объект class Derived. Также я не хочу использовать конструктор по умолчанию class Base, но компилятор ругается.

Как я могу исправить свой код, чтобы увидеть желаемую функциональность?

Ответы [ 2 ]

0 голосов
/ 13 октября 2019

Почему это не может работать?

Этот дизайн не будет работать из-за способа создания объектов.

Когда вы создаете Derived, первое, что происходит, это то, что объект Base создается с помощью конструктора Base. В данный момент нет объекта Derived, поэтому, если бы вы вызывали виртуальную функцию в конструкторе Base, она была бы действительной для класса Base, пока вы не покинете конструктор Base. тело.

Это разрешено стандартом, но с ограничениями:

[base.class.init] / 16 : функции-члены (, включая виртуальныефункции-члены ) могут вызываться для строящегося объекта. (...) Однако, если эти операции выполняются в инициализаторе ctor (или в функции, вызываемой прямо или косвенно из инициализатора ctor) до завершения всех инициализаторов mem для базовых классов, программа имеет неопределенное поведение.

Эти ограничения не распространяются на виртуальные функции, которые вызываются из тела конструктора, поскольку тело выполняется после всех инициализаторов.

Но в вашем случае виртуальная функция является чисто виртуальной для Base. Так что это UB в соответствии со следующим предложением:

[class.abstract] / 6 : функции-члены могут вызываться из конструктора (или деструктора) абстрактного класса;эффект виртуального вызова чистой виртуальной функции прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), равен undefined .

Какова альтернатива

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

Единственная проблема при таком подходе - это риск забыть вызов функции инициализации. Вы можете защитить себя от этого:

  • , используя флаг, чтобы указать, произошла ли инициализация, и проверьте этот флаг во всех функциях-членах (да, это немного накладные расходы).
  • если у вас есть абстрактный класс, вы можете использовать фабричный шаблон (фабричный метод или абстрактную фабрику). Вы можете позволить фабрике сделать звонок.
  • вы можете использовать шаблон компоновщика и убедиться, что конструктор виден только компоновщику, который также не забудет инициализацию.

Другие проблемы с вашим кодом

Вы должны быть осторожны с тем, как вы "вызываете" базовый конструктор из производного конструктора:

class Derived : Base {
public:
    Derived() : Base(42) // this is the correct place !
    {  
    //Base::Base(42);  //<====  OUCH !!! NO !! This creates a temporary base object !!
    }
    ...
};

Вы бытакже нужно быть осторожным с чистыми виртуалами (я не знаю, является ли это опечаткой или ваш компилятор может скомпилировать ваш код):

virtual void someMethod() = 0;  // if it's abstract, no pending {} !
0 голосов
/ 13 октября 2019

Как я могу исправить свой код, чтобы увидеть нужные мне функции?

  1. Удалить вызов чисто виртуальной функции в конструкторе Base.

  2. Вызовите someMethod в конструкторе производного класса, который переопределяет его.

  3. Предоставьте инициализатор подобъекту Base всписок инициаторов участников. Если вы не предоставите инициализатор для базы, он будет инициализирован по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...