在虚成员函数中,除了使用模板,还有什么替代方案?

3

我正在创建一个简单的事件系统,多个监听器可以在特定主题上接收通知,当事件被触发时,它可以将通用有效载荷传递给事件,并且监听器将匹配已触发事件的格式。然而,由于虚函数无法使用模板,那么我该如何实现这一点呢?

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

2
虚函数的目的是动态分派。模板的目的是静态分派。在动态分派函数中无法具有静态确定的类型。它不起作用。 - Silvio Mayolo
一个注意事项 - 在shared_ptr构造函数内部使用make_shared而不是使用new - bartop
@bartop 感谢反馈 - 这有什么原因吗? - jjmcc
@jjmcc 最重要的是 - make_shared 只进行一次堆分配,而 new 版本则需要进行两次 - 第一次为对象,第二次为控制块。在这种情况下使用 new 也可能导致意外的内存泄漏。这篇帖子:https://dev59.com/fGMl5IYBdhLWcg3wkXpx 给出了相当清晰的答案。 - bartop
1个回答

2

一个简单的方法是为负载对象提供一个共同的基础或共同的接口,以便它们不是模板类。

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 和我的尝试?转换是否会有任何性能问题? - jjmcc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接