从没有虚函数的类继承的最佳方法 [英] best way to inherit from a class without virtual functions

查看:60
本文介绍了从没有虚函数的类继承的最佳方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实施一些事件处理.事件有不同类型:integerChangedEvent,boolChangedEvent,stringChangedEvent等.这些事件中的每个事件都包含一些相同的信息,例如:std :: string settingsName,std :: string containerName ...但是,这些不同事件类型中的每一个也都包含一些此事件类型所独有的信息:int double std :: string ... newValue和oldValue.

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.

我不复制相同代码数千次的想法是创建一个名为SettingsEvent的基类.此类应保存所有事件类型也将保存且相同的信息(请参见上文的"settingsName,containerName"),并引起这些信息的设置和获取.

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.

但是C ++(11)确实允许我从没有虚拟方法的类中继承,但是当没有至少一个方法被定义为虚拟时,它不允许我从基类到派生类进行dynamic_cast.但我不想允许这些方法中的任何一种都可以覆盖.我能做什么?有没有一个允许我强制转换为非虚拟类的说明符?

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
};

在代码的另一部分,我想将事件传递给方法.在这种方法中,我想将其转换为IntegerChangedEvent:

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
    }
}

错误消息是:"C2683:'dynamic_cast':'SettingsEvent'不是多态类型

the error message is: "C2683: 'dynamic_cast': 'SettingsEvent' is not a polymorphic type

推荐答案

确定,以便事件知道其类型.

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).

恭喜我们!我们只是重新实现了虚函数调度,可怜,但是我们自己做到了所有,而没有听过那些讨厌的OO伪专家,因此我们可以为此感到自豪巨大的成就!虚拟坏,非虚拟好!

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!

我们本来可以写

event->handle();

并称之为一天,但是那有什么乐趣呢?

and call it a day, but where's the fun in that?

好的,您说的是,但该活动实际上并不知道如何处理自身.这只是一些愚蠢的价值观集合.因此 event-> handle(); 是不可行的.为了实现它,我们将必须引入各种应用程序业务逻辑,可能会创建循环依赖地狱.现在怎么办?

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?

输入访问者.这是专门为处理这种情况而设计的设计模式.它将虚拟调度机制与要通过该机制调用的实际逻辑解耦.虚拟调度是 SettingsEvent 类的职责.逻辑是 EventVisitor 类的职责.因此, EventVisitor 知道如何处理各种事件,而 SettingsEvent 告诉它做什么现在要处理.总体流程与我们最初的开关案例代码没有太大区别,样板代码也没有减少,但是代码结构更清晰,易于修改.您无法添加新的事件类型,而忘记更新旧的处理程序.编译器会大喊大叫.

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 */ }
 };

好吧,您说的来访者已经有几十年了,难道我们不拥有更现代,更苗条,更卑鄙,更时髦的,对15英里半径不感兴趣的1990年代的东西吗?当然可以!C ++ 17为我们带来了 std :: variant std :: visit ,这基本上与旧的访客模式相同,只有 what 部分由 std :: variant 本身处理,而不由任何 Event 处理它拥有.您将所有 SettingsEvent 子类放入 variant 中,它知道下一步该怎么做.不需要虚拟的东西.

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);
   

因此,从史前的Fortran风格的类型分发到基本OO到高级OO,再回到Fortran风格,到现在,我们有了一个完整的圈子,现在有了更多的风格.选择任意一个.

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.

这篇关于从没有虚函数的类继承的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆