Обычно вызов виртуальных функций из конструкторов считается плохой практикой, поскольку переопределенные функции в подобъектах не будут вызываться, поскольку объекты еще не созданы.
Но рассмотрим следующие классы:
class base
{
public:
base() {}
~base() {}
private:
virtual void startFSM() = 0;
};
class derived final : public base
, public fsm_action_interface
{
public:
derived() : base{}
, theFSM_{}
{ startFSM(); }/// FSM interface actions
private:
virtual void startFSM()
{ theFSM_.start(); }
private:
SomeFSMType theFSM_;
}
В этом случае класс derived
помечен как final
поэтому никакие дополнительные суб-объекты не могут существовать. Поэтому виртуальный вызов будет разрешен правильно (для наиболее производного типа).
Это все еще считается плохой практикой?
относительно
» Обычно вызов виртуальных функций из конструкторов считается плохой практикой, поскольку переопределенные функции в подобъектах не будут вызываться, поскольку объекты еще не созданы.
Это не относится к делу. Среди компетентных программистов C ++ обычно не считается плохой практикой вызывать виртуальные функции (кроме чисто виртуальных) из конструкторов, потому что C ++ предназначенный чтобы справиться с этим хорошо. В отличие от таких языков, как Java и C #, где это может привести к вызову метода для еще неинициализированного подобъекта производного класса.
Обратите внимание, что динамическая настройка динамического типа требует затрат времени выполнения.
В языке, ориентированном на предельную эффективность, в качестве основного руководящего принципа «вы не платите за то, что вы не используете», это означает, что это важная и очень намеренная функция, а не произвольный выбор. Это там только для одной цели. А именно для поддержки этих звонков.
относительно
» В этом случае производный класс помечается как окончательный, поэтому дальнейшие подобъекты не могут существовать. Поэтому виртуальный вызов будет разрешен правильно (для наиболее производного типа).
Стандарт C ++ гарантирует, что во время выполнения конструкции для класса T, динамический тип T.
Таким образом, не было проблем с разрешением неправильного типа, во-первых.
относительно
» Это все еще считается плохой практикой?
Это действительно плохая практика объявлять функцию-член «Все еще» не очень значимо virtual
в final
класс, потому что это бессмысленно.или.
Извините, я не видел, чтобы функция виртуального члена была унаследована как таковая.
Рекомендуется использовать ключевое слово для обозначения функции-члена как переопределения или реализации чисто виртуального. override
, не отмечать это как virtual
,
Таким образом:
void startFSM() override
{ theFSM_.start(); }
Это гарантирует ошибку компиляции, если она не переопределение / реализация.
Это все равно будет считаться плохой практикой, так как это почти всегда указывает на плохой дизайн. Вы должны были бы прокомментировать этот код, чтобы объяснить, почему это работает в этом одном случае.
Приведенный выше комментарий Т.С. подтверждает одну из причин, почему это считается плохой практикой.
Что произойдет, если через год вы решите, что
не должно быть окончательным в конце концов?
Тем не менее, в приведенном выше примере шаблон будет работать без проблем. Это потому, что конструктор наиболее производного типа — это тот, который вызывает виртуальную функцию. Эта проблема проявляется, когда конструктор базового класса вызывает виртуальную функцию, которая разрешает реализацию подтипа. В C ++ такая функция не вызывается, потому что во время конструирования базового класса такие вызовы никогда не перейдут к более производному классу, чем тот, который выполняется в данный момент выполняемым конструктором или деструктором. По сути, вы получаете поведение, которого не ожидали.
Редактировать:
Все (правильные / не ошибочные) реализации C ++ должны вызывать версию функции, определенной на уровне иерархии в текущем конструкторе, и не более того.
C ++ FAQ Lite это довольно подробно описано в разделе 23.7.
Скотт Мейерс также взвешивает общую проблему вызова виртуальных функций из конструкторов и деструкторов в Эффективный C ++ Пункт 9
Это может работать, но почему startFSM()
нужно быть virtual
? Вы ни в коем случае не хотите звонить derived::startFSM()
так зачем вообще динамическое связывание? Если вы хотите, чтобы он вызывал то же самое, что и динамически связанный метод, создайте другую не виртуальную функцию с именем startFSM_impl()
и иметь как конструктор, так и startFSM()
позвони вместо этого.
Всегда предпочитайте не виртуальные виртуальные, если вы можете помочь.