【问题标题】:C++ choose method based argument's dynamic typeC ++选择基于方法的参数的动态类型
【发布时间】:2021-11-06 12:59:23
【问题描述】:

问题

假设我有一些模拟器库,它从我这里获取一些对象(也称为事件处理程序)并通过调用它们的 handle_event(Event) 方法为这些对象生成事件。该库为我提供了以下类:

class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class


// Object aka event handler. I should inherit this class and then give objects to the simulator
class AbstractEventHandler
{
public:
        virtual void handle_event(Event) = 0;
};

我想实现一个以不同方式处理不同事件的对象。我想出的第一个代码如下:

#include <iostream>

class MyObject : public AbstractEventHandler
{
public:
        void actual_handle_event(SomeParticularEvent)
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void actual_handle_event(AnotherParticularEvent)
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void actual_handle_event(Event e)
        {
                std::cerr << "Unknown event type occurred\n";
        }

        virtual void handle_event(Event e) override
        {   
                actual_handle_event(e);
        }   
};

С 出乎我的意料,MyObject::handle_event(Event) 将始终调用MyObject::actual_handle_event(Event),而不管e 的动态类型如何。

我的问题是:实现MyObject 的正确方法是什么(最好是可以轻松添加新的事件类型)?


所有代码放在一起

#include <iostream>


class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class


class AbstractEventHandler
{
public:
        virtual void handle_event(Event) = 0;
};


class MyObject : public AbstractEventHandler
{
public:
        void actual_handle_event(SomeParticularEvent)
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void actual_handle_event(AnotherParticularEvent)
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void actual_handle_event(Event e)
        {
                std::cerr << "Unknown event type occurred\n";
        }

        virtual void handle_event(Event e) override
        {
                actual_handle_event(e);
        }
};


int main()
{
        MyObject o{};
        o.handle_event(SomeParticularEvent{}); // Prints "Unknown event type occurred"
}

其他问题

是否也可以创建一个派生自MyObject 的类并实现附加 事件处理程序(不覆盖旧的,但添加对新事件的支持)而不重写handle_event 中的方法派生类?

【问题讨论】:

  • void handle_event(Event e) 按值获取参数,执行对象切片。因此,无论原始对象的类型是什么,您将始终只有一个Event

标签: c++ oop


【解决方案1】:

基于问题中的要求以及在@ypnos 回答的cmets 中讨论的内容,我相信您希望或需要在这里实现访问者模式。

一种可能的实现如下所示(基于关于访问者模式的 Wikipedia 文章:https://en.wikipedia.org/wiki/Visitor_pattern#C++_example

#include <iostream>

// Forward declare all Event classes
class Event;
class SomeParticularEvent;
class AnotherParticularEvent;

class AbstractEventHandler
{
public:
        virtual void handle_event(const Event&) = 0;
        virtual void handle_event(const SomeParticularEvent&) = 0;
        virtual void handle_event(const AnotherParticularEvent&) = 0;
};

class Event {
public:
    // virtual function for accepting the "visitor"
    virtual void accept(AbstractEventHandler& handler) {
        handler.handle_event(*this);
    }
}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {
public:
    // Needs to be overriden to call `handle_event` with the correct type
    void accept(AbstractEventHandler& handler) override {
        handler.handle_event(*this);
    }
}; // Some event class
class AnotherParticularEvent : public Event {
public:
    void accept(AbstractEventHandler& handler) override {
        handler.handle_event(*this);
    }
}; // Some event class

class MyObject : public AbstractEventHandler
{
public:
        void handle_event(const SomeParticularEvent&) override
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void handle_event(const AnotherParticularEvent&) override
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void handle_event(const Event& e) override
        {
                std::cerr << "Unknown event type occurred\n";
        }
};


int main()
{
        MyObject o{};
        SomeParticularEvent{}.accept(o);
        AnotherParticularEvent{}.accept(o);
}

【讨论】:

  • 谢谢。这更接近我想要的。问题是在提供的MyObject 的代码实现中,每次模拟器开发人员决定添加新的事件类型时都需要修改,但我相信这很容易克服:我会添加一个纯虚拟方法AbstractEventHandler::handle_default_event() 和让所有handle_event 方法默认调用它。在这种情况下,对于派生对象,我只需要覆盖我实际需要处理的事件的默认处理程序和方法(如果您看到更好的解决方案,请致电)
  • 您所说的绝对是正确的,并且是访问者模式不可避免的问题。您提出的解决方案当然是处理它的一种方法。在一些更复杂的项目中,您可能还有一个代码生成器来处理生成 AbstractEventHandler 及其所有 virtual 函数以及单个 Events 以及覆盖的 accept 函数,因此您只需手动编写具体的访问者函数(例如:这是 ANTLR 中使用的方法)
【解决方案2】:

在您的方法handle_event() 中发生对象切片。您应该传递 (const) 引用以使继承正常工作,或者传递指针。

但是,在从 handle_event() 内部调度到 actual_handle_event() 时,这对您的情况没有帮助。

蛮力解决方案是通过尝试dynamic_casts 显式调度。您需要在派生类中覆盖 handle_event() 来扩展它。它看起来也不那么漂亮(表明存在设计问题)。

在这些情况下通常会做的是,有问题的对象 (Event) 将提供一个用于查询类型的接口,例如被覆盖的抽象 type() getter。

【讨论】:

  • 似乎 const 引用也不起作用:ideone.com/rOtK85
  • @Kolay.Ne 我猜你可能需要实现访问者模式?
  • @UnholySheep,刚刚在 Wikipedia 上阅读了有关该模式的信息,但不太了解如何在此处应用它。据我了解,事件是要访问的东西,事件处理程序是访问者。但在这种情况下,要么我需要在Event 类中为每个事件类型声明一个方法(但它们是从Event 派生的。甚至可能吗?)或者保持原样,但在这种情况下,再次,通用方法,接受一般Event对象,被调用,而不是特定对象
  • 很抱歉,这不是一个真正的答案。我编辑了我的答案以反映正在发生的事情。不是很有帮助,所以我以后可能会简单地删除它。
  • @Kolay.Ne 您对访问者模式有什么问题?您不能将抽象方法添加到Event
猜你喜欢
  • 1970-01-01
  • 2010-12-07
  • 2020-12-08
  • 1970-01-01
  • 2013-03-17
  • 1970-01-01
相关资源
最近更新 更多