前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【c++11】包装器

【c++11】包装器

作者头像
用户11029103
发布2025-02-03 08:34:13
发布2025-02-03 08:34:13
5600
代码可运行
举报
文章被收录于专栏:技术学习技术学习
运行总次数:0
代码可运行

包装器(Wrapper) 是一个常见的编程设计模式,通常用于封装或“包装”某个现有的对象、函数、数据结构或者操作,以提供额外的功能或简化接口。在不同的上下文中,包装器可能有不同的实现方式和目的,但核心思想都是“将现有功能封装起来,以实现更强的扩展性、易用性或者功能分离”。

1.function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那么我们来看看,我们为什么需要function呢?

代码语言:javascript
代码运行次数:0
复制
ret = func(x);

上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!为什么呢?

代码语言:javascript
代码运行次数:0
复制
template<class F, class T>
T useF(F f, T x)
{
	static int count=0;

	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

useF 是一个模板函数,它接受:

  • 一个函数对象 f(可以是普通函数、函数对象或 Lambda 表达式)。
  • 一个参数 x,这个参数的类型是 T,将被传递给函数对象 f

从结果可以看出,count 的值在每次调用不同 useF 时都被重置为 1,且 count 的地址在每次调用中都不同。这说明 静态变量 count 并没有在多个调用之间共享状态,而是每次调用 useF 都生成了一个独立的 count 变量。

为什么 count 的值和地址不共享?

在模板函数中,静态变量的生命周期是与模板实例相关联的。这意味着每次为不同的模板参数组合生成一个模板实例时,静态变量 count 都是独立的

模板实例化的过程

  1. 在第一次调用 useF(f, 11.11) 时,模板参数 F 被推导为 double (*)(double)(函数指针),T 被推导为 double。这会实例化一个 useF<double (*)(double), double> 模板函数。
    • 对应的 countuseF<double (*)(double), double> 的静态变量,值为 1,地址为 0056B428
  2. 在调用 useF(Functor(), 11.11) 时,模板参数 F 被推导为 FunctorT 仍然是 double。这会实例化另一个独立的模板函数 useF<Functor, double>
    • 对应的 countuseF<Functor, double> 的静态变量,值为 1,地址为 0056B42C
  3. 在调用 useF([](double d) -> double { return d / 4; }, 11.11) 时,模板参数 F 被推导为一个特定的 Lambda 类型(Lambda 表达式的类型是匿名的),Tdouble。这又会实例化一个新的模板函数 useF<LambdaType, double>
    • 对应的 countuseF<LambdaType, double> 的静态变量,值为 1,地址为 0056B430

静态变量在模板中的作用域

  • 每个模板实例的静态变量是独立的,不会共享状态。
  • 对于每一种 FT 的组合,都会实例化一个独立版本的 useF 函数,其静态变量 count 也是独立的
代码语言:javascript
代码运行次数:0
复制
std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

他不是定义可调用对象,而是包装可定义对象

代码语言:javascript
代码运行次数:0
复制
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};


int main()
{
	// 函数名(函数指针)
	function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lambda表达式
	function<int(int, int)> func3 = [](const int a, const int b)
		{return a + b; };
	cout << func3(1, 2) << endl;

	// 类的成员函数
	function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}
类成员函数与普通函数的区别
  1. 普通函数
代码语言:javascript
代码运行次数:0
复制
int f(int a, int b)
{
    return a + b;
}
  • 这是一个普通的全局函数。
  • 普通函数的调用是直接的:f(1, 2),它不依赖于对象或者类的上下文。
  • std::function 可以直接接受普通函数指针,因此不需要加取地址符(&)来指明是函数指针。
  1. 类的静态成员函数
代码语言:javascript
代码运行次数:0
复制
class Plus
{
public:
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return a + b;
    }
};
  • plusi静态成员函数,属于类 Plus,但它不依赖于对象实例。
  • 静态成员函数的调用方式和普通函数类似,可以通过类名直接访问 Plus::plusi,但也可以通过对象实例调用。静态成员函数的行为类似于普通的全局函数,因此它可以作为一个普通函数来传递。

为什么静态成员函数要加取地址符 &?

在调用静态成员函数时,我们通常需要通过类名来指明该函数是属于类的静态函数,而不是实例成员函数。因此,即使是静态成员函数,它也需要通过取地址符 & 来指定其指针类型

代码语言:javascript
代码运行次数:0
复制
function<int(int, int)> func4 = &Plus::plusi;
  • 这里,&Plus::plusi 是静态成员函数的指针,告诉 std::function func4 要存储的是 plusi 函数的地址。
  • 虽然 plusi 是静态成员函数,但它依然是一个函数,并且它的签名是 int(int, int),和普通函数一样,因此我们使用取地址符 & 来获取函数指针。
  1. 类的非静态成员函数
代码语言:javascript
代码运行次数:0
复制
function<double(Plus, double, double)> func5 = &Plus::plusd;
  • plusd 是一个 非静态成员函数,它依赖于类的实例来调用,因为它需要访问类实例的成员数据(如果有的话)。
  • 非静态成员函数的调用需要通过对象实例来绑定:obj.plusd(a, b)。为了将非静态成员函数作为函数指针传递,必须先提供一个对象实例来进行绑定。
为什么非静态成员函数不能直接作为函数指针传递?

非静态成员函数不是普通的全局函数,它是绑定到类的实例上的。也就是说,调用一个非静态成员函数需要一个类的实例,因此它的地址实际上是包含了实例的上下文的。这种成员函数的指针通常被称为成员函数指针,它和普通函数指针有很大的区别。

在 C++ 中,非静态成员函数必须通过对象实例来调用。例如:

代码语言:javascript
代码运行次数:0
复制
Plus p;
p.plusd(1.1, 2.2);  // 通过对象 p 来调用

而在 std::function 中,传递成员函数指针时,需要额外提供一个对象实例或引用来绑定成员函数。可以通过如下方式来实现:

代码语言:javascript
代码运行次数:0
复制
function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
  • 这里的 Plus() 是一个临时对象,它被传递给 func5 作为对象实例,从而调用 plusd 成员函数。

成员函数不能像普通函数一样直接作为指针传递,它们是绑定到对象的,因此不能直接传递函数指针。非静态成员函数需要通过对象实例来绑定,因此我们在 std::function 中也需要传递一个对象实例来确保函数能够正确调用。

代码语言:javascript
代码运行次数:0
复制
#include <functional>
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lambda表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

包装器解决了模版实例化多份的问题

题目链接逆波兰表达式 题目描述

第一种是没有包装器的做法,遇见数字加进去,遇见符号计算

代码语言:javascript
代码运行次数:0
复制
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto& str:tokens)
        {
            if(str == "+" || str == "-" || str == "*" || str == "/")
            {
            int right = st.top();
             st.pop();
             int left = st.top();
             st.pop();
             switch(str[0])
             {
                 case '+':
                     st.push(left+right);
                     break;
                 case '-':
                     st.push(left-right);
                     break;
                 case '*':
                     st.push(left*right);
                     break;
                 case '/':
                     st.push(left/right);
                     break;
             }
             }

         else
         {
             // 1、atoi itoa
             // 2、sprintf scanf
             // 3、stoi to_string C++11
             st.push(stoi(str));
         }      
        }
        return st.top();
    }
};

下面是用包装器

代码语言:javascript
代码运行次数:0
复制
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		map<string, function<int(int, int)>> opFuncMap =
		{
		{ "+", [](int i, int j) {return i + j; } },
		{ "-", [](int i, int j) {return i - j; } },
		{ "*", [](int i, int j) {return i * j; } },
		{ "/", [](int i, int j) {return i / j; } }
		};
		for (auto& str : tokens)
		{
			if (opFuncMap.find(str) != opFuncMap.end())
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				st.push(opFuncMap[str](left, right));
			}
			else
			{
				// 1、atoi itoa
				// 2、sprintf scanf
				// 3、stoi to_string C++11
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

2.bind

std::bind 是 C++11 引入的一个函数模板,用于创建一个新的可调用对象(通常是函数对象)。这个函数对象“绑定”了原始函数的一些参数,并返回一个新的函数,可以通过新的参数进行调用。可以把它看作是部分应用(partial application)的一种实现。 std::bind 允许我们预先绑定一些参数,使得我们可以方便地创建定制化的、部分应用的函数。

基本语法
代码语言:javascript
代码运行次数:0
复制
std::bind(callable, arg1, arg2, ..., argN)
  • callable:可以是普通函数、函数对象、成员函数、或者 Lambda 表达式等。
  • arg1, arg2, …, argN:是你想要绑定的参数,std::bind 会在新的函数调用时预先固定这些参数的值。
返回值

std::bind 返回一个可调用对象(通常是函数对象),该对象能够在稍后的时间接受剩余的参数并执行绑定函数。

常见用法示例
1. 绑定普通函数的参数

假设我们有一个普通函数 add,它接受两个整数并返回它们的和:

代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <functional>  // 引入 std::bind

int add(int a, int b) {
    return a + b;
}

int main() {
    // 绑定 add 函数,第一个参数绑定为 10
    auto add10 = std::bind(add, 10, std::placeholders::_1);  // _1 表示占位符,表示等待一个新的参数
    
    std::cout << add10(5) << std::endl;  // 10 + 5 = 15
    return 0;
}

输出:

代码语言:javascript
代码运行次数:0
复制
15

在这个例子中,std::bindadd 函数的第一个参数绑定为 10,返回一个新的函数 add10,它只需要一个参数来完成调用。std::placeholders::_1 是一个占位符,表示 add10 需要一个新的参数来替代这个占位符。

2. 绑定成员函数

如果你想绑定一个类的成员函数,需要传递一个对象实例来调用成员函数:

代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <functional>  // 引入 std::bind

class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    Calculator calc;

    // 绑定成员函数 add,必须提供对象实例
    auto bound_add = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2);

    std::cout << bound_add(3, 4) << std::endl;  // 3 + 4 = 7
    return 0;
}

输出:

代码语言:javascript
代码运行次数:0
复制
7

在这个例子中,我们使用 std::bind 绑定了类的成员函数 add,并指定了一个对象实例 &calcstd::placeholders::_1std::placeholders::_2 表示调用时提供的两个参数。

3. 绑定成员函数与对象实例

有时你需要绑定成员函数,并且在绑定时固定对象实例,而后续调用时只需要提供其他参数。你可以通过以下方法来完成:

代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <functional>  // 引入 std::bind

class Printer {
public:
    void print(const std::string& str) {
        std::cout << "Printed: " << str << std::endl;
    }
};

int main() {
    Printer printer;

    // 绑定成员函数 print,并固定对象实例
    auto bound_print = std::bind(&Printer::print, &printer, std::placeholders::_1);

    bound_print("Hello, World!");  // 输出: Printed: Hello, World!
    return 0;
}

输出:

代码语言:javascript
代码运行次数:0
复制
Printed: Hello, World!

在这个例子中,std::bindPrinter::print 成员函数与对象 printer 绑定,并返回一个新的函数 bound_print,该函数只需传递一个字符串参数即可。

4. 使用多个占位符

std::bind 支持多个占位符。占位符的编号从 _1 开始,用于指定参数的顺序。

代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <functional>  // 引入 std::bind

int multiply(int a, int b, int c) {
    return a * b * c;
}

int main() {
    // 绑定前两个参数,并保留最后一个参数
    auto bound_multiply = std::bind(multiply, 2, 3, std::placeholders::_1);

    std::cout << bound_multiply(4) << std::endl;  // 2 * 3 * 4 = 24
    return 0;
}

输出:

代码语言:javascript
代码运行次数:0
复制
24

在这个例子中,我们将 multiply 的前两个参数绑定为 2 和 3,使用占位符 _1 表示剩余的参数,最后通过 bound_multiply(4) 提供第三个参数。

5. 绑定 Lambda 表达式

std::bind 不仅支持函数指针,还可以绑定 Lambda 表达式:

代码语言:javascript
代码运行次数:0
复制
#include <iostream>
#include <functional>  // 引入 std::bind

int main() {
    // 绑定 Lambda 表达式,固定第一个参数
    auto bound_lambda = std::bind([](int a, int b) { return a + b; }, 10, std::placeholders::_1);

    std::cout << bound_lambda(5) << std::endl;  // 10 + 5 = 15
    return 0;
}

输出:

代码语言:javascript
代码运行次数:0
复制
15
  1. std::bind 的核心功能:将函数、成员函数或 Lambda 表达式与一些固定的参数绑定,生成一个新的可调用对象,后续调用时可以提供剩余的参数。
  2. std::placeholders::_N:占位符用于指定绑定参数的位置,_1 表示第一个占位符,_2 表示第二个,占位符的顺序决定了参数传递的顺序。
  3. 用途
    • 部分应用:可以在调用函数时预先固定一些参数。
    • 适应某些 API 设计:例如,事件回调、适配器设计等,需要将一部分参数绑定到函数中。
    • 结合算法使用:在 STL 算法(如 std::for_each)中,结合 std::bind 可以生成带有部分固定参数的自定义操作。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.function包装器
    • 类成员函数与普通函数的区别
  • 2.bind
    • 基本语法
    • 返回值
    • 常见用法示例
      • 1. 绑定普通函数的参数
      • 2. 绑定成员函数
      • 3. 绑定成员函数与对象实例
      • 4. 使用多个占位符
      • 5. 绑定 Lambda 表达式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档