Неправильная практика - вызывать виртуальную функцию из конструктора класса, который помечен как final
Обычно вызов виртуальных функций из конструкторов считается плохой практикой, поскольку переопределенные функции в под-объектах не будут вызываться, поскольку объекты еще не построены.
Но рассмотрим следующие классы:
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
, поэтому никаких дополнительных под-объектов не существует. Ergo виртуальный вызов будет корректно разрешен (для самого производного типа).
До сих пор считается плохой практикой?
Ответы
Ответ 1
Относительно
" Обычно вызов виртуальных функций из конструкторов считается плохой практикой, поскольку переопределенные функции в под-объектах не будут вызываться, поскольку объекты еще не созданы.
Это не так. Среди компетентных программистов на C++ он обычно не считается плохой практикой для вызова виртуальных функций (кроме чистых виртуальных) из конструкторов, потому что С++ предназначен для того, чтобы справиться с этим. В отличие от таких языков, как Java и С#, где это может привести к вызову метода для еще неинициализированного под-объекта производного класса.
Обратите внимание, что динамическая настройка динамического типа имеет затраты времени выполнения.
В языке, ориентированном на максимальную эффективность, с "вы не платите за то, что не используете" в качестве основного руководящего принципа, это означает, что это важная и очень умышленная функция, а не произвольный выбор. Это там только для одной цели. А именно для поддержки этих вызовов.
Относительно
" В этом случае полученный класс помечается как final, поэтому никаких дополнительных под-объектов не существует. Ergo виртуальный вызов будет корректно разрешен (для самого производного типа).
Стандарт С++ гарантирует, что во время выполнения построения для класса T динамический тип T.
Таким образом, в первую очередь не было проблемы с решением неправильного типа.
Относительно
" До сих пор считается плохой практикой?
Действительно, плохой практикой является объявление функции-члена virtual
в классе final
, потому что это не имеет смысла. "По-прежнему" не очень значимо .дел >
Извините, я не видел, что виртуальная функция-член унаследована как таковая.
Лучшей практикой для маркировки функции-члена как переопределения или реализации чистого виртуального является использование ключевого слова override
, а не пометить его как virtual
.
Таким образом:
void startFSM() override
{ theFSM_.start(); }
Это обеспечивает ошибку компиляции, если она не является переопределением/реализацией.
Ответ 2
Это все равно будет считаться плохой практикой, так как это почти всегда указывает на плохой дизайн. Вы должны прокомментировать черту из кода, чтобы объяснить, почему это работает в этом случае.
T.C. комментарий выше подтверждает одну из причин, почему это считается плохой практикой.
Что произойдет, если через год по линии вы не должно быть окончательным?
Тем не менее, в приведенном выше примере шаблон будет работать без проблем. Это связано с тем, что конструктор самого производного типа является тем, который вызывает виртуальную функцию. Эта проблема проявляется, когда конструктор базового класса вызывает виртуальную функцию, которая разрешает реализацию подтипа. В С++ такая функция не будет вызвана, потому что при построении базового класса такие вызовы никогда не перейдут к более производному классу, чем к исполняемому в данный момент конструктору или деструктору. По сути, вы оказываетесь в поведении, которого не ожидали.
Edit:
Все (правильные/не-багги) реализации С++ должны вызывать версию функции, определенную на уровне иерархии в текущем конструкторе, и не далее.
С++ FAQ Lite подробно описывает это в разделе 23.7.
Скотт Мейерс также взвешивает общую проблему вызова виртуальных функций от конструкторов и деструкторов в Эффективном С++. Пункт 9
Ответ 3
Он может работать, но почему startFSM()
должен быть virtual
? Ни в коем случае вы на самом деле не хотите называть что-либо, кроме derived::startFSM()
, так зачем вообще иметь динамическое связывание? Если вы хотите, чтобы он вызывал то же самое, что и динамически привязанный метод, создайте еще одну не виртуальную функцию под названием startFSM_impl()
и попросите вместо нее конструктор и startFSM()
.
Всегда предпочитайте виртуальный виртуальный, если вы можете ему помочь.