问题
I am trying to implement some event handling. There are different types of events: integerChangedEvent, boolChangedEvent, stringChangedEvent and so on... Each of these events hold some of the same information like: std::string settingsName, std::string containerName... But also each of these different event types also hold some information which are unique for this event types: e.g. int double std::string... newValue and oldValue.
My idea to not copy the same code thousands of times is to make a base class called SettingsEvent. This class should hold the information which all event types also would hold and which are same (see above "settingsName, containerName") and of cause the setter and getter of these information.
All other events can inherit this base class and add their own members / methods.
so far, everything is fine.
But C++ (11) does allow me to inherit from a class without virtual methods, but it does not allow me to dynamic_cast from the base to the derived class, when not at least one method is defined as virtual. But I don't want to allow, that any of these methods are overwrite able. What can I do? Is there a specifier which allows me to cast a non-virtual class?
for better understanding, here is some piece of code:
class SettingsEvent
{
public:
std::string getSettingsName() const;
std::string getSettingsContainerName() const;
// some more methods I don't want to write down know... ;)
protected:
SettingsEvent(); //protected constructor, to ensure nobody creates an object of this base class
private:
std::string m_settingsName;
std::string m_settingsContainerName;
// some more members I also don't want to write down know...
};
class IntegerChangedEvent : public SettingsEvent
{
public:
IntegerChangedEvent(); //public constructor, it is allowed to create an object of this class
int getNewValue() const;
int getOldValue() const;
//also here are more methods I don't want to list
private:
int m_newValue;
int m_oldValue;
//also more members I don't want to list
};
On another part in my code I want to pass the event to a method. In that method I want to cast it into the IntegerChangedEvent:
void handleEvent(SettingsEvent* event)
{
//to be honest, the event itself knows what kind of event it is (enum) but lets say it is an IntegerChangedEvent to keep it simple
IntegerChangedEvent* intEvent = dynamic_cast<IntegerChangedEvent*>(event);
if(intEvent)
{
//do stuff
}
}
the error message is: "C2683: 'dynamic_cast': 'SettingsEvent' is not a polymorphic type
回答1:
OK so the event knows what type it is.
switch (event->type)
{
case IntegerChangedEventType: {
IntegerChangedEvent* ievent = static_cast<IntegerChangedEventType*>(event);
handleIntegerChangedEvent(ievent);
break;
}
case StringChangedEventType: {
StringChangedEvent* sevent = static_cast<StringChangedEventType*>(event);
handleStringChangedEvent(sevent);
break;
}
// ... etc etc etc
}
(You can use either static or dynamic cast; dynamic cast obviously requires at least one virtual function; static cast is perfectly OK if you are sure events don't lie about their types).
Congratulations to us! We've just reimplemented virtual function dispatch, poorly, but we did it all by ourselves without listening to all those pesky OO pseudo-gurus, and we can proud ourselves on this tremendous achievement! Virtual bad, non-virtual good!
We could have written
event->handle();
and call it a day, but where's the fun in that?
OK you say, but the event doesn't really know how to handle itself. It's just a little dumb collection of values. So event->handle();
is not feasible. In order to implement it, we would have to bring in all kinds of application business logic, possibly creating circular dependency hell. What now?
Enter visitor. It's a design pattern invented specifically to handle this situation. It decouples virtual dispatch mechanism from the actual logic to be called via this mechanism. The virtual dispatch is the responsibility of the SettingsEvent
class. The logic is the responsibility of the EventVisitor
class. So EventVisitor
knows how to handle various events, and SettingsEvent
tells it what to handle right now. The overall flow is not much different from our initial switch-case code, and the boilerplate is nor even reduced, but the code is more structured and easy to modify. There's no way you add a new event type and forget to update old handlers. The compiler will yell at ya.
class EventVisitor
{
virtual void handle(IntegerChangedEvent& ev) = 0;
virtual void handle(StringChangedEvent& ev) = 0;
};
class SettingsEvent
{
virtual void accept (EventVisitor& vis) = 0;
};
class IntegerChangedEvent: public SettingsEvent
{
void accept (EventVisitor& vis) override { vis.handle(*this); }
};
class StringChangedEvent: public SettingsEvent
{
void accept (EventVisitor& vis) override { vis.handle(*this); }
};
// actual event handling logic
class AppEventHandler : public EventVisitor
{
void handle(IntegerChangedEvent& ev) override { /* specific logic */ }
void handle(StringChangedEvent& ev) override { /* specific logic */ }
};
OK you say, but the visitor is a couple decades old, don't we have something more modern, lean, mean, hipster-friendly, and not reeking of 1990s over a 15 mile radius? Sure we do! C++17 brings us std::variant and std::visit, which is basically the same as the visitor pattern of old, only the what part is handled by std::variant
itself and not by any Event
it holds. You put all your SettingsEvent
subclasses inside a variant
, and it knows what to do next. No virtual anything needed.
using AnyEvent = std::variant<IntegerChangedEvent, StringChangedEvent, ...>;
AnyEvent event = ...;
std::visit(overloaded
{
[](IntegerChangedEvent& ev) { ... },
[](StringChangedEvent& ev) { ... },
}, event);
So here we are, having made a full circle, from the prehistoric Fortran-style dispatch on types through basic OO through advanced OO and back to Fortran-style, but now with a lot more style. Choose whichever you like.
回答2:
If you're inheriting a class, you should have virtual destructors to create a vtable, which would then allow a dynamic cast.
In the spirit of "prefer containment to inheritance", why not simply contain SettingsEvent
in your IntegerChangedEvent
? Also in the spirit of "use inheritance to make similar things behave differently" and "use templates to do the same thing with different types" could you not use a templated ChangedEvent
class?
Take a look at this example: How to store templated objects of different type in container?
来源:https://stackoverflow.com/questions/63686677/best-way-to-inherit-from-a-class-without-virtual-functions