没有虚拟调度的默认方法实现

Default method implementation without virtual dispatch

本文关键字:默认 方法 实现 调度 虚拟      更新时间:2023-10-16

好的,所以我想我也想吃我的蛋糕。我认为这是不可能的,但我们开始了。

我正在研究一个简单的事件系统,其中回调是在某些用户定义的侦听器类中定义的,事件发射器将该类作为模板参数。采用模板参数的原因是我想避免使用虚拟调度调用侦听器方法。

// Class in charge of emitting events.
template<typename TListener = DefaultEmptyListener>
class Emitter {
public:
    Emitter(TListener& l) : mListen(l) {}
    EmitFoo(...) {
       mListen.OnFoo(...);
    }
    EmitBar(...) {
       mListen.OnBar(...);
    }
private:
    TListener& mListen;
};
class SomeUserDefinedListener() {
   void OnFoo() {} // Not virtual
   // OnBar(); --> Not defined. Want default implementation.
}

我的问题是我想避免让用户必须为所有可能的事件定义"On*"方法。也就是说,如果用户未提供方法,我希望侦听器类具有事件方法的默认实现。

有没有办法在不引入虚拟调度的情况下为侦听器提供默认实现?

当你处理在编译时已知确切类型的对象(即没有动态调度(时,你不需要virtual。 只需创建一个基类,其中包含所有处理程序方法的默认实现,以及一个重写要自定义的处理程序的派生类,并将派生类作为模板参数传递。

如果您希望能够将类作为TListener模板参数传递,但随后mListen变量实际上引用派生类的实例,则需要将处理程序函数声明为 virtual 。 但是,如果TListener始终是mListen引用的对象的确切类型,则不需要virtual

(顺便说一句,"虚拟继承"与普通的虚函数不同,我认为这就是你所指的。

您可以使用没有虚拟多态性的继承:

// Class in charge of emitting events.
template<typename TListener = DefaultEmptyListener>
class Emitter {
public:
    Emitter(TListener& l) : mListen(l) {}
    EmitFoo(...) {
       mListen.OnFoo(...);
    }
    EmitBar(...) {
       mListen.OnBar(...);
    }
private:
    TListener& mListen;
};
class SomeUserDefinedListener() : public DefaultEmptyListener {
   void OnFoo() {} // Not virtual
   // OnBar(); --> Not defined. DefaultEmptyListener implementation.
}
您可以在

C++17 中利用if constexpr和检测习惯用语(其中ns::is_detected std::experimental::is_detected或类似(轻松做到这一点

// Class in charge of emitting events.
template<typename TListener = DefaultEmptyListener>
class Emitter {
public:
    Emitter(TListener& l) : mListen(l) {}
    EmitFoo(...) {
        if constexpr (ns::is_detected_v<decltype(mListen.OnFoo(...)>>)
        { mListen.OnFoo(...); }
        else
        { /* Default empty behaviour */ }
    }
    EmitBar(...) {
        if constexpr (ns::is_detected_v<decltype(mListen.OnBar(...)>>)
        { mListen.OnBar(...); }
        else
        { /* Default empty behaviour */ }
    }
private:
    TListener& mListen;
};

这可以使用正常的SFINAE方法向后移植到C++14/C++11。