Контекст
У меня есть FSM, в котором каждое состояние представлено как класс. Все состояния происходят из общего базового класса и имеют одну виртуальную функцию для обработки ввода.
Поскольку одновременно может быть активным только одно состояние, все возможные состояния хранятся в объединении внутри класса FSM.
Проблема
Поскольку все состояния (включая базовый класс) хранятся по значению, я не могу использовать виртуальный диспетчер напрямую. Вместо этого я создаю ссылку на базовый объект в объединении, используя static_cast, а затем вызываю виртуальный метод через эту ссылку. Это работает на GCC. Это не работает на Clang.
Вот минимальный пример:
#include <iostream>
#include <string>
struct State {
virtual std::string do_the_thing();
virtual ~State() {}
};
struct IdleState: State {
std::string do_the_thing() override;
};
std::string State::do_the_thing() {
return "State::do_the_thing() is called";
}
std::string IdleState::do_the_thing() {
return "IdleState::do_the_thing() is called";
}
int main() {
union U {
U() : idle_state() {}
~U() { idle_state.~IdleState(); }
State state;
IdleState idle_state;
} mem;
std::cout
<< "By reference: "
<< static_cast<State&>(mem.state).do_the_thing()
<< "\n";
std::cout
<< "By pointer: "
<< static_cast<State*>(&mem.state)->do_the_thing()
<< "\n";
}
Когда я компилирую этот код с GCC 8.2.1, вывод программы:
By reference: IdleState::do_the_thing() is called
By pointer: State::do_the_thing() is called
Когда я компилирую его с помощью Clang 8.0.0, вывод:
By reference: State::do_the_thing() is called
By pointer: IdleState::do_the_thing() is called
Таким образом, поведение двух компиляторов инвертировано: GCC выполняет виртуальную диспетчеризацию только через ссылку, Clang только через указатель.
Одно решение, которое я нашел, это использовать reinterpret_cast<State&>(mem)
(так что приведение из самого объединения к State&
). Это работает на обоих компиляторах, но я все еще не уверен, насколько это переносимо. И причина, по которой я поместил базовый класс в объединение, заключалась в том, чтобы специально избегать reinterpret_cast, во-первых ...
Так каков правильный способ принудительной виртуальной отправки в таких случаях?
Обновление
Подводя итог, можно сказать, что одним из способов сделать это является наличие отдельного указателя типа базового класса вне объединения (или std :: variable), который указывает на текущий активный член.
Прямой доступ к подклассу в объединении, как если бы это был базовый класс, небезопасен.