C++11中的包装器

文章目录


引言

在C++中,可调用对象类型五花八门(函数指针、仿函数、lambda表达式、类成员函数等),它们的逻辑功能十分相似,但是类型差异巨大,导致代码声明繁琐、接口适配困难、扩展性不足。而C++11通过引入std::function包装器,抹平了不同类型间的差异;引入std::bind包装器,灵活调整参数列表,以适配各种接口。


1.std::function

1.1.什么是std::function

std::function是C++11提供的类模板包装器 ,定义在<functional>头文件中,核心作用是消除不同可调用对象的类型差异。无论是普通函数、lambda还是仿函数,都能被std::function包装成同一类型。

cpp 复制代码
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • Ret:可调用对象的返回值类型
  • Args...:可调用对象的参数列表

1.2.核心用法

1.2.1.包装普通函数

普通函数是最基础的可调用对象:

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

int add(int a, int b)
{
	return a + b;
}
int main()
{
	//包装add函数,类型匹配int<int, int>
	function<int(int, int)> func_add = add;
	cout << "1 + 2 = " << func_add(1, 2) << endl;
	return 0;
}

运行结果:

1.2.2.包装仿函数

仿函数是重载operator()的类实例

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;
struct Sub {
	int operator()(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	function<int(int, int)> func_sub = Sub();
	cout << "5 - 3 = " << func_sub(5, 3) << endl;
	return 0;
	
}

运行结果:

1.2.3.包装lambda表达式

lambda是匿名函数对象,function能直接与之匹配

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

int main()
{
	function<int(int, int)> func_mul = [](int a, int b) {return a * b;};
	cout << "3 * 4 = " << func_mul(3, 4) << endl;
	return 0;
}

运行结果:

1.2.4.包装类成员函数

包装类成员函数需要额外处理this指针,function需在参数列表中显式声明this对应的对象类型(指针或引用 );

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

class Divide {
public:
	//非静态成员函数,参数隐含this指针
	double div(double a, double b)
	{
		return a / b;
	}

	//静态成员函数,不含this指针
	static double div_static(double a, double b)
	{
		return a / b;
	}
};
int main()
{
	Divide d;
	//包装非静态成员函数,包含this指针
	function<double(Divide*, double, double)> func_div = &Divide::div;//非静态成员函数必须取地址,不能隐式类型转换
	cout << "func_div: " << "12.2 / 2 = " << func_div(&d, 12.2, 2) << endl;

	//包装静态成员函数,不包含this指针
	function<double(double, double)> func_stat_div = Divide::div_static;//取地址可加可不加
	cout << "func_stat_div: " << "12.2 / 2 = " << func_stat_div(12.2, 2) << endl;
	return 0;
}

运行结果:

1.2.5.空包装器

若std::function是空包装器(未绑定任何可调用对象),调用时会抛异常(std::bad_function_call),注意捕获。

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

int main()
{
	function<int(int, int)> func_empty;
	try {
		func_empty(3, 4);
	}
	catch (const bad_function_call& e) {
		cout << "error: " << e.what() << endl;
	}
	return 0;
}

运行结果:

1.3.什么时候适合用std::function

  • 作为容器元素:如map<string,function<Arg...>>,实现字符串-函数映射
  • 作为函数参数/返回值:传递回调函数时,不需要关注函数的具体类型(函数指针/仿函数/lambda),匹配函数签名(返回值类型、参数类型和数量)即可。
  • 简化类型声明:替代复杂的函数指针类型,如:int(*)(int ,int),代码更简洁。

2.std::bind

2.1.什么是std::bind

std::bind是C++11提供的函数模板 ,定义在<functional>头文件中,核心作用是调整可调用对象的参数列表(绑定固定参数、重新排序参数)

cpp 复制代码
//simple(1)	
template <class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);
//with return type (2)	
template <class Ret, class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);

一般形式:auto newCallable = bind(callable, arg_list)

  • callable:原可调用对象
  • arg_list:参数列表
  • 参数列表中,placeholders::_n(如_1、_2)是占位符,表示新函数的第n个参数,未用占位符的参数会被"固定绑定"。

2.2.核心用法

2.2.1.绑定固定参数(减少参数)

假设我们有一个计算乘法的函数mul,如果我们想固定其中一个因数a为100, 只用输入另一个因数b,此时可用bind 实现

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
int mul(int a, int b)
{
	return a * b;
}
int main()
{
	//绑定_a == 100, 新函数第一个参数_1传给b
	auto mul_fixed_a = bind(mul, 100, _1);
	cout << "100 * 5 = " << mul_fixed_a(5) << endl;

	////绑定_b == 200, 新函数第一个参数_1传给a
	auto sub_fixed_b = bind(mul, _1, 200);
	cout << "3 * 200 = " << mul_fixed_a(3) << endl;
	return 0;
}

运行结果:

2.2.2.重排参数顺序

如果要调整函数参数顺序,可以通过调整占位符顺序实现

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
int sub(int a, int b)
{
    return a - b;
}
int main() {
    // 原函数是 a - b,重排后变成 b - a(_2 是新函数第2个参数,_1 是第1个)
    auto sub_swap = bind(sub, _2, _1);
    cout << "5 - 10 = " << sub_swap(10, 5) << endl; // 输出:(5-10)*10=-50
    return 0;
}

运行结果:

2.2.3.结合成员函数使用

非静态成员函数中参数包含隐藏的this指针 ,因此bind绑定时需要包含该类的引用或指针参数

代码示例:

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;

class Calculator {
public:
    double multiply(double a, double b) {
        return a * b;
    }
};

int main() {
    Calculator calc;
    // 绑定 calc 作为 this 指针,_1 和 _2 是新函数的两个参数
    auto calc_mul = bind(&Calculator::multiply, &calc, _1, _2);
    cout << "3 * 4 = " << calc_mul(3, 4) << endl; // 输出:12
    return 0;
}

运行结果:

2.3.什么时候适合用std::bind

  • 适配参数不匹配的接口:比如一个第三方库函数需要传三个参数,但我的函数只有两个参数,这时可以用bind绑定一个参数,即可适配
  • 简化重复调用:若某函数频繁用相同参数调用,可以用bind绑定,简化函数

3.实战案例

3.1.逆波兰表达式求值(function + map优化版本)

function + map --> "操作符 - 函数"映射

代码示例:

cpp 复制代码
#include <stack>
#include <vector>
#include <string>
#include <functional>
#include <map>
using namespace std;

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        // 用 map 存储"操作符-function"映射
        map<string, function<int(int, int)>> op_map = {
            {"+", [](int a, int b) { return a + b; }},
            {"-", [](int a, int b) { return a - b; }},
            {"*", [](int a, int b) { return a * b; }},
            {"-", [](int a, int b) { return a - b; }},
            {"/", [](int a, int b) { return a / b; }},
            {"%", [](int a, int b) { return a % b; }}
        };

        for (auto& str : tokens) {
            if (op_map.count(str)) { // 匹配到操作符
                int right = st.top(); st.pop();
                int left = st.top(); st.pop();
                // 调用对应的 function
                st.push(op_map[str](left, right));
            }
            else {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

3.2.复利计算

在计算复利时,需要传入三个参数:利率、本金、年限,如果我们想固定利率和年限,只输入本金,则可用bind简化函数。

代码示例:

cpp 复制代码
#include <functional>
#include <iostream>
using namespace std;
using namespace placeholders;

int main() {
    // 复利计算 lambda:rate=年利率,money=本金,year=年限,返回利息
    auto calc_interest = [](double rate, double money, int year) -> double {
        double ret = money;
        for (int i = 0; i < year; i++) {
            ret += ret * rate; // 每年复利
        }
        return ret - money; // 利息 = 最终金额 - 本金
    };

    // 用 bind 固定利率和年限,生成专用函数(只需传入本金)
    function<double(double)> interest_3y_1_5 = bind(calc_interest, 0.015, _1, 3);  // 3年1.5%利率
    function<double(double)> interest_5y_2_0 = bind(calc_interest, 0.020, _1, 5);  // 5年2.0%利率
    function<double(double)> interest_10y_2_5 = bind(calc_interest, 0.025, _1, 10); // 10年2.5%利率

    // 调用简化版函数
    cout << "100万 3年1.5%利息:" << interest_3y_1_5(1000000) << endl; // 约45678.38
    cout << "100万 5年2.0%利息:" << interest_5y_2_0(1000000) << endl; // 约104080.80
    cout << "100万 10年2.5%利息:" << interest_10y_2_5(1000000) << endl; // 约280084.50

    return 0;
}

运行结果:


4.注意事项

  • function非静态成员函数时,由于非静态成员函数参数中隐藏了this指针,因此function参数列表必须包含this指针对应的对象类型(指针或引用),否则编译报错。
  • bind占位符 在命名空间std::placeholders中,使用前记得显式声明using namespace std::placeholders
  • 若用bind绑定引用参数,需用ref()cref()包装,否则bind会默认按值传递。
cpp 复制代码
void print(int& x) { x++; cout << x << endl; }
int main() {
    int a = 10;
    auto func = bind(print, ref(a)); // 用 ref 绑定引用
    func(); // 输出 11,a 被修改为 11
    return 0;
}
  • 包装器底层是函数对象封装,无额外性能消耗,不必担心效率问题。

结语

std::function 和 std::bind 作为 C++11 引入的强大工具,共同为处理复杂的函数调用场景带来了革命性的简化:它们消除了不同可调用对象(如普通函数、Lambda 表达式、成员函数)之间的类型壁垒,使得函数的传递、存储和调用变得前所未有的统一和灵活。开发者无需再为每种回调类型定义繁琐的函数指针或适配器,std::function 提供了一个通用的 "容器",而 std::bind 则像一个万能的 "适配器",能够轻松调整函数签名,固定参数或重排参数顺序,极大地提升了代码的表达力和可维护性。

相关推荐
讨厌下雨的天空1 小时前
Linux信号量
java·开发语言
爱吃烤鸡翅的酸菜鱼2 小时前
Spring Boot 实现 WebSocket 实时通信:从原理到生产级实战
java·开发语言·spring boot·后端·websocket·spring
雪域迷影2 小时前
C++中编写UT单元测试用例时如何mock非虚函数?
开发语言·c++·测试用例·gmock·cpp-stub开源项目
AI街潜水的八角3 小时前
Python电脑屏幕&摄像头录制软件(提供源代码)
开发语言·python
hadage2333 小时前
--- git 的一些使用 ---
开发语言·git·python
lly2024065 小时前
HTML与CSS:构建网页的基石
开发语言
一只会写代码的猫5 小时前
面向高性能计算与网络服务的C++微内核架构设计与多线程优化实践探索与经验分享
java·开发语言·jvm
是小胡嘛7 小时前
C++之Any类的模拟实现
linux·开发语言·c++