从没有虚函数的类继承的最佳方法

时间:2020-09-01 11:22:43

标签: c++ c++11 inheritance

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

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

所有其他事件都可以继承此基类并添加自己的成员/方法。

到目前为止,一切都很好。

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

为了更好地理解,这是一些代码:

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:

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'不是多态类型

2 个答案:

答案 0 :(得分:3)

确定,以便事件知道它是什么类型。

 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
 }

(您可以使用静态或动态类型转换;动态类型转换显然至少需要一个虚函数;如果您确定事件的类型不属于静态类型,则静态类型转换完全可以。)

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

我们本来可以写

event->handle();

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

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

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

 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::variantstd::visit,这与旧的访客模式基本相同,只有 what 部分由std::variant本身处理而不是由其持有的任何Event。您将所有SettingsEvent子类放在variant中,它知道下一步要做什么。不需要虚拟的东西。

using AnyEvent = std::variant<IntegerChangedEvent, StringChangedEvent, ...>;

AnyEvent event = ...;
std::visit(overloaded 
           {
             [](IntegerChangedEvent& ev) { ... },
             [](StringChangedEvent& ev) { ... },
           }, event);
   

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

答案 1 :(得分:0)

如果您要继承一个类,则应该使用虚拟析构函数来创建一个vtable,然后再进行动态转换。

本着“更喜欢包含而不是继承”的精神,为什么不简单地在您的SettingsEvent中包含IntegerChangedEvent?同样,本着“使用继承使相似的事物表现不同”和“使用模板对不同类型进行相同的事物”的精神,您不能使用模板化的ChangedEvent类吗?

看看这个例子: How to store templated objects of different type in container?