C++:运行时通过基类指针识别派生类的实现疑问
Hey there! Let's walk through your state machine design question—you're currently using function pointers for states, want to create separate classes for each signal, and are considering a typeid-based approach to let state functions handle specific signal types. Let's break this down.
Is using typeid a valid approach?
Short answer: Yes, it works, but it comes with tradeoffs that are worth weighing against your project's needs.
Using typeid to check the runtime type of a signal object lets you branch logic in your state functions like this:
void SomeState::handleSignal(Signal* signal) { if (typeid(*signal) == typeid(ButtonPressSignal)) { // Handle button press logic } else if (typeid(*signal) == typeid(TimeoutSignal)) { // Handle timeout logic } // ... other signals }
This is straightforward to implement, especially if you have a small, fixed set of signal types that won't change often.
Why does the Signal class need a virtual function?
Great observation! typeid relies on Runtime Type Information (RTTI) to return the actual derived type of an object. For this to work correctly with a base class pointer/reference, the base class (Signal) must be polymorphic—meaning it has at least one virtual function.
If Signal has no virtual functions, typeid(*signal) will return the type of the base class (not the derived one) because the compiler can't track the runtime type without a vtable. The easiest way to fix this is to make the Signal destructor virtual (this also prevents memory leaks when deleting derived objects via base pointers):
class Signal { public: virtual ~Signal() = default; // Makes Signal polymorphic };
Alternatives to typeid (More OOP-Friendly)
While typeid works, it can violate the Open/Closed Principle—every time you add a new signal type, you have to go into every state function and add a new else if branch. For larger projects or ones where signals/states will evolve, the Visitor Pattern is a better fit.
Here's a quick outline of how it works:
- Define a
StateVisitorinterface with avisitmethod for each signal type. - Make each
Signalderived class implement anacceptmethod that calls the correspondingvisitmethod on the visitor. - Each state class implements
StateVisitor, providing the logic for handling each signal type in the matchingvisitmethod.
Example code snippet:
// Forward declarations class ButtonPressSignal; class TimeoutSignal; class StateVisitor { public: virtual void visit(ButtonPressSignal* signal) = 0; virtual void visit(TimeoutSignal* signal) = 0; }; class Signal { public: virtual ~Signal() = default; virtual void accept(StateVisitor* visitor) = 0; }; class ButtonPressSignal : public Signal { public: void accept(StateVisitor* visitor) override { visitor->visit(this); } }; // Concrete state implementing the visitor class IdleState : public StateVisitor { public: void visit(ButtonPressSignal* signal) override { // Handle button press in idle state } void visit(TimeoutSignal* signal) override { // Handle timeout in idle state } };
Now, instead of checking types in your state logic, you just call signal->accept(this), and the correct handler is invoked automatically. Adding a new signal only requires adding a new visit method to StateVisitor and implementing accept in the new signal class—no need to modify existing state code.
Which approach should you choose?
- Use
typeidif: You have a small number of signals/states, and the set is unlikely to grow. It's quick to implement and easy to understand. - Use the Visitor Pattern if: You expect to add new signals or states frequently, or want to adhere strictly to OOP design principles. It's more scalable but adds some initial boilerplate.
Either way, making your Signal class polymorphic (with a virtual destructor, at minimum) is a must if you're working with derived signal types via base pointers.
内容的提问来源于stack exchange,提问作者dr.umma

