У меня есть следующий сценарий:
class Caller{
public:
Caller() {...}
void register(Base* b) {...}
void callBase() { b->virt()}
};
class Base {
public:
Base(Caller c) { println("Base::Base()"); c.register(this); sleep(30); }
virtual void virt() { println("Base::virt()"); }
};
class Derived : public Base {
public:
Derived() { println("Derived::Derived()"); }
virtual void virt() { println("Derived::virt()"); }
};
Я обычно знаю, если кто-то вызывает virt для производного класса, то будет вызываться Derived :: virt (). Но здесь, если функция callBase вызывается, когда Base спит в родительском конструкторе, какая функция будет вызвана? Base :: virt () или Derived :: virt ()?
Спасибо
Согласно C ++ Lite FAQ 23.5, Base::virt()
будет называться.
Во всяком случае, это действительно не то, что вы хотите сделать — если ваш объект используется другим потоком без надлежащей инициализации, вы можете столкнуться со всевозможными неприятными условиями гонки. Например, что произойдет, если второй поток называется virt
именно тогда, когда Vtable установлен? Кто может гарантировать, что установка vtable во время строительства объекта является атомарной операцией?
Вы должны разработать свой код таким образом, чтобы ваши объекты не использовались до полной инициализации.
Было бы позвонить Base::virt
, До завершения конструктора базового класса виртуальные функции отправляются так, как если бы динамический тип был Base
,
(Хотя технически у вас было бы неопределенное поведение, если бы другой поток обращался к объекту без надлежащей синхронизации. И программа не будет компилироваться с тех пор register
это ключевое слово.)
В то время как в конструкторе или деструкторе для Base
класс действует почти во всех отношениях, как если бы он был Base
,
Это означает, что Base
-применение для любой вызываемой виртуальной функции.
Если нет (что возможно, потому что Base
может объявить его чисто виртуальным, делая Base
абстрактный класс), вы получите Неопределенное поведение, так что все может случиться.
Часто наблюдаемые варианты поведения в этом случае включают сбой при недопустимом доступе, преднамеренный прерывание с ошибкой и вызов переопределения некоторых производных классов.
Если вызов выполняется одновременно (многопоточность / сигналы), стандарт говорит, что вы идете прямо к UB.
Java, C # и тому подобное имеют совершенно разные правила, поэтому изучите это для каждого нового языка в отдельности.
В сторону: использование register
как имя в C ++ опрометчиво. Это недопустимо, потому что это несуществующее (то есть никакого эффекта) ключевое слово класса хранения.