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

C++如何向模板类构造函数传入可调用对象并支持后续调用

实现方案

核心采用固定签名的轻量类型擦除设计,既不需要给MyClass增加额外模板参数,也不要求可调用对象继承特定基类,同时支持引用持有外部可调用对象,保证有状态仿函数的状态跨实例共享。

核心逻辑

  • 在MyClass<T>内部定义抽象可调用基类,仅约定invoke(T) -> float的调用接口和克隆接口
  • 针对任意传入的可调用类型,派生模板化的实现类,内部仅持有原可调用对象的引用,不做拷贝
  • MyClass内部通过基类指针持有擦除类型后的可调用包装,构造时根据传入的可调用类型自动实例化对应的实现类
  • 正确实现拷贝/移动构造和赋值函数,满足std::variant和std::vector对元素可拷贝/移动的要求
#include <variant>
#include <vector>
#include <functional>
#include <iostream>
#include <utility>

template <typename T>
class MyClass {
    // 类型擦除抽象基类
    struct CallableBase {
        virtual float invoke(T arg) = 0;
        virtual CallableBase* clone() const = 0;
        virtual ~CallableBase() = default;
    };

    // 具体可调用类型的包装实现,仅持有原对象引用
    template <typename F>
    struct CallableImpl final : CallableBase {
        F& func_ref;
        explicit CallableImpl(F& f) : func_ref(f) {}
        float invoke(T arg) override {
            return func_ref(std::move(arg));
        }
        CallableBase* clone() const override {
            return new CallableImpl<F>(func_ref);
        }
    };

    T a;
    CallableBase* f;

public:
    // 构造函数自动推导传入的可调用类型
    template <typename Callable>
    MyClass(T _a, Callable* _f)
        : a(std::move(_a)), f(new CallableImpl<Callable>(*_f)) {}

    ~MyClass() {
        delete f;
    }

    // 实现深拷贝,避免浅拷贝导致的重复释放
    MyClass(const MyClass& other)
        : a(other.a), f(other.f->clone()) {}

    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            delete f;
            a = other.a;
            f = other.f->clone();
        }
        return *this;
    }

    MyClass(MyClass&& other) noexcept
        : a(std::move(other.a)), f(other.f) {
        other.f = nullptr;
    }

    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete f;
            a = std::move(other.a);
            f = other.f;
            other.f = nullptr;
        }
        return *this;
    }

    float getFloat() {
        return f->invoke(a);
    }
};
关键注意事项
  • 之前出现空指针调用问题,核心原因通常是两点:一是类型擦除层未正确初始化基类指针,二是传入临时可调用对象的地址产生悬垂引用;使用时需保证传入的可调用对象生命周期长于持有它的MyClass实例
  • 传入lambda时需要先将lambda实例化为具名变量再取地址传入,C++不允许直接取临时lambda对象的地址,同时该写法也能保证lambda生命周期符合要求
  • 实现中仅持有可调用对象的引用,不会拷贝原对象,因此有状态仿函数的计数会在所有指向它的MyClass实例间共享,完全符合预期逻辑
  • MyClass仅保留T作为模板参数,因此std::variant<MyClass<int>, MyClass<float>, MyClass<bool>>的定义完全合法,可直接存入vector统一遍历
测试验证

以下测试代码完全匹配需求中的调用逻辑:

// 测试用可调用对象定义
static float minus1(int num) {
    return num - 1;
}

class Stateful {
public:
    Stateful() : num_calls(0) {}
    float operator()(int num) {
        ++num_calls;
        return static_cast<float>(num) - (static_cast<float>(num_calls) * 0.5f);
    }
private:
    int num_calls;
};

static std::function<float(float)> invert = [](float a){ return -a; };

int main() {
    using Element = std::variant<MyClass<int>, MyClass<float>, MyClass<bool>>;
    std::vector<Element> vec;

    MyClass<int> option1(-5, &minus1);
    MyClass<int> option1a(99, &minus1);
    std::cout << option1.getFloat() << std::endl;  // 输出-6
    std::cout << option1a.getFloat() << std::endl; // 输出98

    static Stateful stateful{};
    MyClass<int> option2(10, &stateful);
    std::cout << option2.getFloat() << std::endl;  // 输出9.5
    std::cout << option2.getFloat() << std::endl;  // 输出9

    MyClass<int> option2a(100, &stateful);
    std::cout << option2a.getFloat() << std::endl; // 输出98.5
    std::cout << option2.getFloat() << std::endl;  // 输出8

    MyClass<float> option3(1.602f, &invert);
    MyClass<float> option3a(-6.022f, &invert);
    std::cout << option3a.getFloat() << std::endl; // 输出6.022
    std::cout << option3.getFloat() << std::endl;  // 输出-1.602

    float pi = 3.14f;
    // lambda先实例化为具名变量再传地址
    auto pi_lambda = [&pi](bool b){return (b ? -pi : 0.f);};
    MyClass<bool> option4(true, &pi_lambda);
    std::cout << option4.getFloat() << std::endl;  // 输出-3.14

    // 放入vector遍历测试
    vec.push_back(option1);
    vec.push_back(option2);
    vec.push_back(option2a);
    vec.push_back(option3);
    vec.push_back(option3a);
    vec.push_back(option4);

    for (auto& option : vec) {
        std::visit([](auto&& arg) { std::cout << arg.getFloat() << std::endl; }, option);
    }

    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 05:15:31