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

如何设计支持比较操作的C++回调?实现列表中查找与移除

如何定义支持相等比较的多类型回调(静态/成员函数+捕获lambda)

注意:此问题并非询问“如何比较std::function对象的相等性”(通常结论是无法直接实现),核心需求是如何定义一种可同时容纳静态函数、成员函数以及带捕获的lambda的回调类型,使其能存入容器并支持相等比较,用于回调注册时去重和移除操作。

场景背景

最初使用简单的函数指针作为回调类型:

typedef bool(*Callback)(const MyClass&);

随着设计推进,需要支持成员函数作为回调,因此改用带捕获的lambda:

auto myLambda = [this](const MyClass& myclass) {...};

后续切换到std::function来统一回调类型:

typedef std::function<bool(const MyClass&)> Callback;

但将Callback存入std::vector后,尝试的查找代码完全无效,所有比较都会返回匹配:

bool findCallback(const Callback& cb, const std::vector<Callback>& callback_v) {
    std::find_if(callback_v.begin(), callback_v.end(),[cb](Callback& other) {
        return *cb.target<bool(const MyClass&)>()==*other.target<bool(const MyClass&)>();
    }) != callback_v.end();
}

原因是std::function::target()仅对存储的原始类型(如函数指针)有效,对于lambda、绑定的成员函数等会返回nullptr,解引用会导致未定义行为,自然无法区分不同的回调对象。


解决方案

方案1:自定义回调包装类,内置相等比较逻辑

自己实现一个包装类,统一处理不同类型的可调用对象,并为每种类型实现对应的相等判断逻辑:

#include <functional>
#include <typeindex>
#include <typeinfo>
#include <memory>

class Callback {
public:
    // 包装静态函数/函数指针
    Callback(bool(*func)(const MyClass&)) 
        : m_impl(new FuncImpl(func)) {}

    // 包装成员函数(绑定对象指针)
    template <typename T>
    Callback(T* obj, bool(T::*mem_func)(const MyClass&))
        : m_impl(new MemFuncImpl<T>(obj, mem_func)) {}

    // 包装带捕获的lambda(要求lambda可复制且支持==比较)
    template <typename F>
    Callback(F func, typename std::enable_if_t<!std::is_same_v<F, Callback>>* = nullptr)
        : m_impl(new LambdaImpl<F>(std::move(func))) {}

    // 相等比较运算符
    bool operator==(const Callback& other) const {
        if (m_impl->type() != other.m_impl->type()) return false;
        return m_impl->equals(other.m_impl.get());
    }

    bool operator!=(const Callback& other) const {
        return !(*this == other);
    }

    // 调用回调
    bool operator()(const MyClass& arg) const {
        return m_impl->invoke(arg);
    }

private:
    // 抽象基类,定义统一接口
    struct Impl {
        virtual ~Impl() = default;
        virtual std::type_index type() const = 0;
        virtual bool invoke(const MyClass&) const = 0;
        virtual bool equals(const Impl*) const = 0;
    };

    // 静态函数的具体实现
    struct FuncImpl : Impl {
        bool(*func)(const MyClass&);
        FuncImpl(bool(*f)(const MyClass&)) : func(f) {}
        std::type_index type() const override { return std::type_index(typeid(FuncImpl)); }
        bool invoke(const MyClass& arg) const override { return func(arg); }
        bool equals(const Impl* other) const override {
            auto o = static_cast<const FuncImpl*>(other);
            return func == o->func;
        }
    };

    // 成员函数的具体实现
    template <typename T>
    struct MemFuncImpl : Impl {
        T* obj;
        bool(T::*mem_func)(const MyClass&);
        MemFuncImpl(T* o, bool(T::*mf)(const MyClass&)) : obj(o), mem_func(mf) {}
        std::type_index type() const override { return std::type_index(typeid(MemFuncImpl<T>)); }
        bool invoke(const MyClass& arg) const override { return (obj->*mem_func)(arg); }
        bool equals(const Impl* other) const override {
            auto o = static_cast<const MemFuncImpl<T>*>(other);
            return obj == o->obj && mem_func == o->mem_func;
        }
    };

    // Lambda的具体实现
    template <typename F>
    struct LambdaImpl : Impl {
        F func;
        LambdaImpl(F f) : func(std::move(f)) {}
        std::type_index type() const override { return std::type_index(typeid(LambdaImpl<F>)); }
        bool invoke(const MyClass& arg) const override { return func(arg); }
        bool equals(const Impl* other) const override {
            auto o = static_cast<const LambdaImpl<F>*>(other);
            return func == o->func;
        }
    };

    std::unique_ptr<Impl> m_impl;
};

使用时,静态函数、绑定的成员函数、带捕获的lambda都可以包装成Callback对象,直接存入std::vector<Callback>,并通过operator==进行相等比较,适配std::find等算法。

注意:C20之前,带捕获的lambda默认不支持==操作;C20及以后,同类型的无捕获lambda支持==,若捕获的对象本身支持比较,也可以正常使用该逻辑。

方案2:为每个回调分配唯一ID,通过ID管理

如果不需要真正比较回调的逻辑,仅需实现注册去重和移除,可以为每个回调分配唯一标识,通过ID来操作:

#include <vector>
#include <functional>
#include <unordered_map>

class CallbackNotifier {
public:
    using Callback = std::function<bool(const MyClass&)>;
    using CallbackID = std::size_t;

    // 注册回调,返回唯一ID
    CallbackID registerCallback(Callback cb) {
        CallbackID id = m_nextID++;
        m_callbacks[id] = std::move(cb);
        return id;
    }

    // 通过ID移除回调
    bool unregisterCallback(CallbackID id) {
        return m_callbacks.erase(id) > 0;
    }

    // 判断ID对应的回调是否已注册
    bool isRegistered(CallbackID id) const {
        return m_callbacks.find(id) != m_callbacks.end();
    }

    // 通知所有回调
    void notify(const MyClass& arg) {
        for (const auto& [id, cb] : m_callbacks) {
            cb(arg);
        }
    }

private:
    std::unordered_map<CallbackID, Callback> m_callbacks;
    CallbackID m_nextID = 0;
};

这种方式实现简单、性能高效,客户端只需要保存注册时返回的ID即可完成后续操作,无需处理复杂的相等比较逻辑。缺点是无法通过回调对象本身进行查找,必须依赖ID。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 12:59:57