You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带回调的定制仿函数类的惯用替代方案是什么?

背景

当你查看某个代码库时,如果看到如下代码:

class A {
    virtual ~A() = default;
    virtual void act() = 0;
};

你可能会说:“这本质上就是一个无输入无输出的函数式对象(或仿函数)的接口。完全可以去掉这个类,把如下代码:

void foo(A& a) { a.act(); }

替换为:

template <std::invocable F> 
void foo(F& f) { f(); }

并将所有继承自A的类对象替换为任意可调用对象,用其operator()实现原act()方法的功能。

当然,在C++20之前的代码中也能实现相同效果,用std::enable_if或static_assert()确保只有可调用对象被传入foo()。

实际问题

现在假设你看到如下基类定义:

class B {
   virtual ~B() = default;

   /// act()完成内部工作后会调用这些回调
   void addCallback(const CallbackType& callback);

   virtual void act();
};

它不仅是一个(有状态的)仿函数,还包含一系列回调函数。

那么可以用哪种泛型惯用法替代如下函数:

void foo(B& b) { b.act(); }

同时避免使用定制类B?

注意事项:

  • 可以假设b的具体类型在编译期已知。
  • 请不要建议使用所有B子类的std::variant,这不是我想要的方案。

解决方案

我们可以通过编译期接口约束的泛型惯用法来替代基类B的继承约束,核心是要求传入的类型必须满足「拥有act()方法」和「拥有addCallback()方法」这两个条件,具体实现分C++20和更早版本两种情况:

C++20 版本(使用概念)

先定义一个概念来描述符合要求的类型:

#include <concepts>

template<typename T>
concept ActWithCallbacks = requires(T& t, const CallbackType& cb) {
    { t.act() } -> std::same_as<void>;
    { t.addCallback(cb) } -> std::same_as<void>;
};

然后改写foo函数:

template<ActWithCallbacks T>
void foo(T& b) {
    b.act();
}

只要传入的类型T同时拥有void act()和void addCallback(const CallbackType&)成员函数,就能通过编译,完全不需要依赖基类B。

C++20 之前版本(使用SFINAE或静态断言)

如果需要兼容更早的C++标准,可以用std::enable_if结合decltype做SFINAE约束:

#include <type_traits>

template<typename T>
auto foo(T& b) -> typename std::enable_if_t<
    std::is_same_v<decltype(std::declval<T&>().act()), void> &&
    std::is_same_v<decltype(std::declval<T&>().addCallback(std::declval<const CallbackType&>())), void>
> {
    b.act();
}

或者用static_assert做编译期断言,让错误提示更直观:

template<typename T>
void foo(T& b) {
    static_assert(std::is_same_v<decltype(b.act()), void>, "传入类型必须包含void act()成员函数");
    static_assert(std::is_same_v<decltype(b.addCallback(std::declval<const CallbackType&>())), void>, "传入类型必须包含void addCallback(const CallbackType&)成员函数");
    b.act();
}

额外说明

这种方案的优势在于完全基于鸭子类型的编译期检查,不需要继承体系,同时保留了原基类B的接口约束能力。如果后续需要扩展接口要求,只需修改概念或断言条件即可。

内容的提问来源于stack exchange,提问作者einpoklum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:22:59