воскресенье, 1 апреля 2012 г.

Вся правда о виртуальных вызовах в конструкторах.


Что выведет такая программа?

class Base {
public:
  Base() {
    Init();
  }
  virtual void Init() {
    std::cout << "Base::Init()" << std::endl;
  }
};

class Derived : public Base {
public:
  virtual void Init() {
    std::cout << "Derived::Init()" << std::endl;
  }
};

int main() {
  Derived derived;
}

Функция Init() виртуальная. Значит, при вызове такой функции при конструировании объекта Derived будет задействована таблица виртуальных функций класса Derived, и вызовется Derived::Init().

Если у вас были подобные иллюзии, запустите этот код и окончательно развейте их.

Почему же так происходит?

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

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

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called in the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object (1.8).

Никакой технической проблемы с реализацией виртуальных вызовов из конструкторов никогда не было. Это не артефакт какого-то хитрого устройства механизма виртуальных функций. Как справедливо замечает Страуструп, «реализовать небезопасный вызов виртуальной функции из конструктора было бы гораздо проще». Главная мотивация этой «поправки для конструкторов» — обезопасить программиста, защитить дурака от самого себя.

Ну а чтобы обезопасить себя еще больше, следуйте девятому правилу Мейерса.

Комментариев нет:

Отправить комментарий