用std::function减缓C++模板代码膨胀和编译压力的一个场景

注意:其实std::function也是模板,也会产生多个实例,但对于下面的例子来说,减少了实例的范围。

https://mp.weixin.qq.com/s/okDFMAUHUBWunTObYkluDQ

C++模板的一个很大的缺点是会较大拖慢编译的速度,有的时候有时让人难以接受了。

问题示例:多次实例化

假设有一个模板函数 use_f,它接受一个可调用对象 f 和一个 double 值,然后调用 f 并返回结果:

cpp 复制代码
template <typename F>
double use_f(F f, double x) {
    return f(x);
}

现在我们有三种不同类型的可调用对象(函数、函数对象、lambda),但它们的调用特征标都是 double(double)

cpp 复制代码
double cube(double x) { return x * x * x; }

struct Square {
    double operator()(double x) const { return x * x; }
};

auto neg = [](double x) { return -x; };

如果我们分别调用 use_f(cube, 5.0)use_f(Square{}, 5.0)use_f(neg, 5.0),编译器会生成三个不同的 use_f 实例,因为 cubeSquareneg 的类型完全不同(函数指针类型、类类型、闭包类型)。这不仅造成代码冗余,而且当可调用对象数量增多时问题会更加明显。

解决方案:使用 std::function

std::function 定义在头文件 <functional> 中,它的模板参数是调用特征标。例如 std::function<double(double)> 可以包装任何接受一个 double 并返回 double 的可调用对象。

我们修改 use_f,使其参数类型不再是模板参数,而是固定的 std::function<double(double)>

cpp 复制代码
double use_f(std::function<double(double)> f, double x) {
    return f(x);
}

此时,无论我们传入函数指针、函数对象还是 lambda,use_f 的参数类型都是相同的 std::function<double(double)>,因此整个程序只存在一个 use_f 函数实例(普通函数,而非模板)。当然,也可以保留模板形式,但显式指定 Fstd::function<double(double)>,不过更自然的做法是直接使用普通函数。

完整代码示例

下面是一个完整的示例,展示了如何使用 std::function 包装器统一处理多个调用特征标相同的可调用对象,并且只实例化一次 use_f

cpp 复制代码
#include <iostream>
#include <functional>   // for std::function

// 普通函数
double cube(double x) {
    return x * x * x;
}

// 函数对象(仿函数)
struct Square {
    double operator()(double x) const {
        return x * x;
    }
};

int main() {
    // 几个调用特征标均为 double(double) 的可调用对象
    double (*func_ptr)(double) = cube;          // 函数指针
    Square func_obj;                            // 函数对象
    auto lambda = [](double x) { return -x; };  // lambda 表达式

    // 使用 std::function 包装器统一类型
    std::function<double(double)> wrapper1 = func_ptr;
    std::function<double(double)> wrapper2 = func_obj;
    std::function<double(double)> wrapper3 = lambda;
    // 也可以直接传入可调用对象,因为 std::function 的构造函数是隐式的

    // 定义一个接受 std::function 参数的普通函数
    // 这个函数只会被实例化一次,因为参数类型固定
    auto use_f = [](std::function<double(double)> f, double x) -> double {
        std::cout << "调用 use_f,函数地址: " << &f << std::endl; // 演示只存在一个实例
        return f(x);
    };

    double x = 5.0;

    std::cout << "cube(5) = " << use_f(wrapper1, x) << std::endl;
    std::cout << "square(5) = " << use_f(wrapper2, x) << std::endl;
    std::cout << "negate(5) = " << use_f(wrapper3, x) << std::endl;

    // 也可以直接传递可调用对象,std::function 会隐式构造
    std::cout << "直接传递 lambda: " << use_f([](double x){ return x + 10; }, 3.0) << std::endl;

    return 0;
}

输出示例(具体地址可能不同):

复制代码
调用 use_f,函数地址: 0x7ffc1234
cube(5) = 125
调用 use_f,函数地址: 0x7ffc1234
square(5) = 25
调用 use_f,函数地址: 0x7ffc1234
negate(5) = -5
调用 use_f,函数地址: 0x7ffc1234
直接传递 lambda: 13

可以看到,每次调用 use_f 时,f 的地址都相同,说明 use_f 只有一个实例(lambda 表达式的地址相同进一步证明函数体只有一个)。

进一步说明

  • 性能考虑std::function 会引入一定的运行时开销(类型擦除、可能的内存分配),但通常可以忽略不计。如果性能极其敏感,且可调用对象类型在编译期已知,模板方案更优。但在需要统一接口或减少代码膨胀的场景下,std::function 是很好的选择。

  • 与模板对比 :模板方案为每种类型生成独立代码,没有运行时开销,但会导致代码膨胀。std::function 方案只生成一份代码,但调用时通过虚函数或函数指针间接跳转,略有开销。两者各有适用场景。

  • C++23 补充 :C++23 引入了 std::move_only_function,适用于只移类型的可调用对象;C++26 可能有更多改进,但 std::function 依然是日常最通用的工具。

总结

当多个可调用对象具有相同的调用特征标时,利用 std::function 包装器可以将它们统一为同一个类型,从而避免模板函数或模板类的多次实例化,简化代码并减少编译产物体积。上述示例清晰地展示了如何重写程序,使得原本需要多次实例化的 use_f 只存在一个实例,同时保留了调用不同类型可调用对象的灵活性。

相关推荐
长谷深风11113 小时前
Java 面试高频:反射机制与异常体系全面解析
java·开发语言·面试·exception·java 反射·java 异常·class 对象
fantasy_arch14 小时前
BasicVSR-lite图像画质增强
开发语言·pytorch
Rust语言中文社区14 小时前
【Rust日报】2026-05-24 Secluso v1.0.2 版本发布
开发语言·后端·rust
玖釉-14 小时前
二叉树展开为链表:从先序遍历到原地指针重排
c++·windows·算法·leetcode·链表
吃好睡好便好15 小时前
矩阵的加减运算
开发语言·人工智能·学习·线性代数·算法·matlab·矩阵
吃好睡好便好15 小时前
提取矩阵特定多行元素
开发语言·线性代数·算法·matlab·矩阵
Mister西泽15 小时前
C++ Primer Plus 第六版 编程练习题及详细答案
开发语言·c++·学习·visual studio
Qt程序员15 小时前
从上电到系统就绪:ARM+U-Boot 嵌入式 Linux 启动流程
linux·运维·c++·内核·设备树·嵌入式·ram
froginwe1115 小时前
Python 循环嵌套
开发语言
@大迁世界15 小时前
AI还替不了的JS能力
开发语言·前端·javascript·人工智能·ecmascript