Когда безопасно вызывать виртуальную функцию в конструкторе - PullRequest
4 голосов
/ 21 января 2012

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

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

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

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

Ответы [ 3 ]

5 голосов
/ 21 января 2012

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

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

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

3 голосов
/ 21 января 2012

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

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

2 голосов
/ 21 января 2012

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

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

Это не похоже на проблему, это просто одна из техместа, склонные к ошибкам.

...