Когда безопасно вызывать виртуальную функцию в конструкторе

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

Вот что я делаю:

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

Так что, по моему мнению, создание объекта должно быть завершено, когда я выполняю виртуальный вызов, и все должно быть в порядке. Есть ли что-нибудь, что может пойти не так? Я предполагаю, что мне придется пометить эту часть логики некоторыми большими комментариями, чтобы объяснить, почему эту логику никогда не следует перемещать ни в одно из базовых предложений, даже если кажется, что ее можно было бы переместить. Но кроме глупости других программистов у меня все должно быть в порядке, не так ли?

3 ответа

Решение

Абсолютно безопасно вызывать любую неабстрактную виртуальную функцию в конструкторе или деструкторе! Тем не менее, его поведение может сбивать с толку, поскольку он может не делать то, что ожидается. Во время выполнения конструктора класса статический и динамический тип объекта является типом конструктора. То есть виртуальная функция никогда не будет отправлена ​​на переопределение другого производного класса. Помимо этого, виртуальная диспетчеризация фактически работает: например, при вызове виртуальной функции через указатель базового класса или ссылку правильно отправляет переопределение в классе, который в настоящее время является конструктором или уничтожен. Например (вероятно, изобилует опечатками, так как в настоящее время я не могу этот код):

#include <iostream>
struct A {
    virtual ~A() {}
    virtual void f() { std::cout << "A::f()\n"; }
    void g() { this->f(); }
};
struct B: A {
    B() { this->g(); } // this prints 'B::f()'
    void f() { std::cout << "B::f()\n"; }
};
struct C: B {
    void f() { std::cout << "C::f()\n"; } // not called from B::B()
};

int main() {
    C c;
}

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

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

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

Правило не столько в том, что вы должны быть в листовом классе, сколько для того, чтобы понять, что когда вы делаете вызов участника из Foo::Foo(..)объект является точно Foo, даже если он на пути к тому, чтобы быть Bar (при условии, Foo происходит от Bar и вы создаете Bar пример). Это на 100% надежно.

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

Похоже, у вас нет проблем, это всего лишь одно из тех мест, где могут возникать ошибки.

Другие вопросы по тегам