【C++11】包装器及其应用

C++11推出包装器,主要是为了解决函数式编程、泛型编程和回调机制中的几个核心痛点。这些包装器主要包括std::function、std::bind。

一、std::function

C++98/03都面临一个核心问题,就是无法统一表示"可调用的东西",例如以下的代码:

cpp 复制代码
double calculateA(int a, int b, double rate)
{
	return rate * (a + b);
}

double calculateB(int a, int b, double rate)
{
	return rate * (a + b + 1);
}

struct calculateC
{
	double operator()(int a, int b, double rate)
	{
		return rate * (a + b + 2);
	}
};

auto calculateD = [](int x, int y, double z)->double { return (x + y + 3) * z; };

在 C++98 中,有多种"可以像函数一样调用"的实体:

  • 普通函数:double calculateA(int a, int b, double rate);
  • 函数指针:void (*f)(int) = calculateA;
  • 函数对象(仿函数):struct calculate { double operator()(int a, int b, double rate) {} };
  • 成员函数指针:&MyClass::method

但它们类型完全不同,无法用同一个类型变量存储!于是在C++11中就提出了包装器std::function

cpp 复制代码
#include<functional>

double calculateA(int a, int b, double rate)
{
	return rate * (a + b);
}

double calculateB(int a, int b, double rate)
{
	return rate * (a + b + 1);
}

struct calculateC
{
	double operator()(int a, int b, double rate)
	{
		return rate * (a + b + 2);
	}
};

//包装器
int main()
{
	int a = 1, b = 2;
	double rate = 1.5;
	auto calculateD = [](int x, int y, double z)->double { return (x + y + 3) * z; };
	//以上代码中calculateA、calculateB、calculateC、calculateD基本上类型不同且要实现的功能是有差别的,
	//但是这四个有一个大致相同的功能趋向,且返回值和参数一致。
	//那么就可以对这些进行整合。从而就提出了包装器的概念。
	std::function<double(int, int, double)> calA = calculateA;
	std::function<double(int, int, double)> calB = calculateB;
	std::function<double(int, int, double)> calC = calculateC();
	std::function<double(int, int, double)> calD = calculateD;
	std::cout << calA(a, b, rate) << std::endl;
	std::cout << calB(a, b, rate) << std::endl;
	std::cout << calC(a, b, rate) << std::endl;
	std::cout << calD(a, b, rate) << std::endl;

	return 0;
}

如上代码使用std::function<Ret(Args...)>来完成对这些调用对象类型的统一。这时就因为类型的统一,就可以对这统一的对象进行管理、存储及使用,leetcode中150. 逆波兰表达式求值 - 力扣(LeetCode)

cpp 复制代码
#include<vector>
#include<functional>
#include<map>
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        map<string,function<int(int,int)>> RPN={
            {"+",[](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;}}
        };
        stack<string> st;
        for(auto& x:tokens)
        {
            if(RPN.count(x))//是符号
            {
                int right=stoi(st.top());
                st.pop();
                int left = stoi(st.top());
                st.pop();
                st.push(to_string(RPN.find(x)->second(left,right)));
            }
            else
            {
                st.push(x);
            }
        }
        return stoi(st.top());
    }
};

以上代码中,将所有的计算都存储在RPN这个对象中,就很好的整合了这些代码,并且对比不用包装器的代码,很清楚的就可以发现使用包装器可以减少一些工作量,并且如果后续更新运算也只需要对RPN做出更改,而不需要更改其他任何地方,使得代码的耦合降低。

二、std::bind

std::bind函数定义在头文件中,是一个函数模板,他就像一个函数包装器,接受一个调用对象,生成一个新的调用对象来"适应"原对象的参数列表。使用std::bind就可以把一个原本接收N个参数的函数,通过绑定一些参数返回M(<N)个参数的新函数。同时可以实现参数顺序调整的操作。

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);

上面是bind的模板

cpp 复制代码
#include<functional>

int sub(int a, int b)
{
	return a - b;
}

double calculateA(int a, int b, double rate)
{
	return rate * (a - b);
}



class calculateC
{
public:
	double operator()(int a, int b, double rate)
	{
		return rate * ((double)a - (double)b + 2);
	}
	double calculateB(int a, int b, double rate)
	{
		return rate * (a - b);
	}
};

//包装器
int main()
{
	int a = 1, b = 2;
	double rate = 1.5;
	//改变接收参数顺序
	std::function<int(int, int)> rsub1 = bind(sub, placeholders::_1, placeholders::_2);
	std::function<int(int, int)> rsub2 = bind(sub, placeholders::_2, placeholders::_1);
	cout << rsub1(a, b) << endl;//-1
	cout << rsub2(a, b) << endl;// 1

	//绑定参数rate
	std::function<double(int, int)> ccalA1 = bind(calculateA, placeholders::_2, placeholders::_1,0.5);
	std::function<double(int, int)> ccalA2 = bind(calculateA, placeholders::_2, placeholders::_1, 1.5);
	calculateC calC;
	std::function<double(int, int)> ccalA3 = bind(&calculateC::calculateB, calC, placeholders::_2, placeholders::_1, 2.5);

	cout << ccalA1(a, b) << endl;//0.5
	cout << ccalA2(a, b) << endl;//1.5
	cout << ccalA3(a, b) << endl;//2.5

	return 0;
}
复制代码
在代码中
复制代码
std::function<double(int, int)> ccalA3 = bind(&calculateC::calculateB, calC, placeholders::_2, placeholders::_1, 2.5);

必须传入对象 calC(或其引用/指针) ,而 ❌ 不能写成 calculateC::calculateB()


🔍 一、根本原因:成员函数 ≠ 普通函数

在 C++ 中:

  • 普通函数(如 sub, calculateA 是独立的,可以直接调用。
  • 成员函数(如 calculateC::calculateB 必须通过某个对象实例 才能调用,因为它隐式依赖 this 指针。

也就是说:

复制代码
calC.calculateB(a, b, rate); // ✅ 正确:通过对象 calC 调用
calculateC::calculateB(a, b, rate); // ❌ 错误!缺少 this 指针

💡 成员函数本质上是:
double calculateB(calculateC* this, int a, int b, double rate)

所以,当你取成员函数地址时:

复制代码
&calculateC::calculateB  // 类型是:double (calculateC::*)(int, int, double)

它只是一个"函数指针",没有绑定到任何对象,不能直接调用!


✅ 二、std::bind 如何调用成员函数?

std::bind 的第一个参数是 成员函数指针第二个参数必须是该类的对象(或引用/指针) ,用来提供 this

复制代码
bind(&Class::member_func, object_or_ptr, arg1, arg2, ...)

你的例子分解:

复制代码
bind(
    &calculateC::calculateB,   // 成员函数指针
    calC,                      // 提供 this → bind 内部会调用 calC.calculateB(...)
    placeholders::_2,          // 对应第一个 int 参数(实际是 b)
    placeholders::_1,          // 对应第二个 int 参数(实际是 a)
    2.5                        // 固定 rate = 2.5
)

最终效果相当于:

复制代码
// 当你调用 ccalA3(a, b) 时,内部执行:
calC.calculateB(b, a, 2.5);  // 注意参数顺序被交换了!

❌ 三、为什么不能写 calculateC::calculateB()

尝试 1:直接写函数名(不取地址)

复制代码
bind(calculateC::calculateB, ...) // ❌ 编译错误!
  • calculateC::calculateB 不是可调用对象(除非是 static 成员函数)
  • 它不是一个值,不能作为表达式传递

尝试 2:试图"调用"它

复制代码
bind(calculateC::calculateB(), ...) // ❌ 更错!
  • calculateC::calculateB()调用一个不存在的对象的成员函数 → 非法
  • 即使能编译,也会立即执行一次(返回一个 double),而不是生成一个可调用对象

🚫 成员函数必须和对象绑定才能形成可调用实体!

相关推荐
guygg884 小时前
两轮车MATLAB仿真程序的实现方法
开发语言·matlab
汉克老师4 小时前
GESP2025年12月认证C++七级真题与解析(单选题8-15)
c++·dfs·bfs·二分·强联通分量·gesp7级·gesp七级
yugi9878384 小时前
异构网络下信道环境建模方法及应用
开发语言·网络
小北方城市网4 小时前
第 11 课:Python 全栈项目进阶与职业发展指南|从项目到职场的无缝衔接(课程终章・进阶篇)
大数据·开发语言·人工智能·python·数据库架构·geo
Thetimezipsby4 小时前
Go(GoLang)语言基础、知识速查
开发语言·后端·golang
以太浮标4 小时前
华为eNSP模拟器综合实验之-BGP路由协议的配置解析
服务器·开发语言·php
fqbqrr5 小时前
2601C++,pmr管理内存
c++
君义_noip5 小时前
【模板:矩阵加速递推】信息学奥赛一本通 1642:【例 2】Fibonacci 第 n 项
c++·线性代数·矩阵·信息学奥赛·csp-s
宠..5 小时前
优化文件结构
java·服务器·开发语言·前端·c++·qt
源码梦想家5 小时前
多语言高性能异步任务队列与实时监控实践:Python、Java、Go、C++实战解析
开发语言·python