【问题标题】:What is an alternative to using templates on a virtual member function?在虚拟成员函数上使用模板的替代方法是什么?
【发布时间】:2018-02-03 20:49:42
【问题描述】:

我正在创建一个简单的事件系统,其中可以通知多个侦听器关于特定主题的通知,并且当触发事件时,它可以将通用有效负载传递给事件,并且侦听器将匹配触发事件的格式。但是,由于无法在虚函数上使用模板,我还能如何实现呢?

class AEventListener
{
public:

    template<class T>
    struct PayloadObject {
        T obj;
    };

    explicit AEventListener();
    virtual ~AEventListener();

//error here because T is undefined. Each PayloadObject may have a different type
    virtual void notify(vector<shared_ptr<PayloadObject<T>>> payload) = 0;
};

当事件主题订阅了侦听器时会调用 notify 方法,但我想要一种通用方法,将大量随机对象传递给侦听器。

例如

fireEvent("test.topic", Payload { 0, "hello", 123 });
//...
listener.notify(payload);

我将如何在 C++ 中解决这个问题?


我已经设法解决了这个问题,尽管我认为这不是最好的方法并且可能会降低性能。

template<class T>
struct PayloadObject : public APayloadObject {
    T obj;

    PayloadObject(T obj) {
        this->obj = obj;
    }

    ~PayloadObject() override {

    };

};

struct APayloadObject {
    virtual ~APayloadObject();
};

开火:

vector<shared_ptr<APayloadObject>> payload;
payload.push_back(shared_ptr<PayloadObject<int>>(new PayloadObject<int>(5))); //payload[0] = int - 5
Events::fire(EventKeys::DISCONNECTION_EVENT, payload);

通知:

shared_ptr<PayloadObject<int>> number = dynamic_pointer_cast<PayloadObject<int>>(payload[0]);
int id = number.get()->obj; //payload[0] = int - 5

【问题讨论】:

  • 虚函数的重点是动态调度。模板的重点是静态调度。您不能在动态调度的函数中拥有静态确定的类型。那样不行。
  • 一个注意事项 - 使用 make_shared 而不是在 shared_ptr 构造函数中使用 new
  • @bartop 感谢您的反馈 - 这是有原因的吗?
  • @jjmcc 最重要的是 - make_shared 分配一个堆,而 new 版本分配两个 - 第一个用于对象,第二个用于控制块。在这样的上下文中使用new 也会导致意外的内存泄漏。这篇文章:stackoverflow.com/questions/18301511/… 给出了非常明确的答案

标签: c++ templates


【解决方案1】:

一种简单的方法是为 Payload 对象提供一个通用基础或通用接口。这样它们就不是模板类了。

struct Payload {
  virtual ~Payload() = default;
  virtual std::string foo() const;
  virtual std::string bar() const;
};

另一种方法是对负载对象使用变体类型:

using Message_t = boost::variant<A, B, C>;

然后让AEventListener 采用Message_t 类型,这样就不需要成员函数是模板。

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notify(std::vector<Message_t> payload) = 0;
};

在 C++17 中,您可以使用 std::variant 代替 boost。

另一种方法是跳过使用变体,而只是让监听器必须实现三个不同的函数,每个类型一个:

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notifyA(A payload) = 0;
    virtual void notifyB(B payload) = 0;
    virtual void notifyC(C payload) = 0;
};

更一般地说,在 C++ 中很难创建一个概念,比如“可以使用任何特定类型的参数调用的函数对象”。这部分是因为......它不是很有用,对于任何类型的数据,您一般都无法做很多事情,您可以一无所知。

因此,我建议您仔细考虑完善您的事件侦听器概念,并更具体地说明这种类型的对象实际上应该被要求做什么。

【讨论】:

  • 我已经选择了有效负载的基类,这似乎可以从我尝试过的内容中获得。最好像您建议的那样使其更具体,但如果有很多事件,那么所有定义可能会变得混乱。您可以通过我的尝试快速查看我的 OP 吗?选角会有性能问题吗?
猜你喜欢
  • 2023-04-10
  • 1970-01-01
  • 2011-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多