C++中的bind

目录

本文说明:

一:bind的意义

二:bind的用法

1:第一个参数

2:其余参数

a:重排参数顺序

b:绑定部分参数

c:混合使用

三:bind绑定类的成员函数

1:传递对象指针 (&obj) 

2:传递对象副本 (obj) 

3:使用占位符延迟绑定 (_1) 

4:传递对象引用 

四:bind的终极用法


本文说明:

在Linux后期中,经常会用到bind,但bind这东西是在C++的中后期学的,导致博主总是想不起来bind的用法是什么,所以特此为bind写一篇使用说明,以后遇到bind就可以来这温习一下~~

一:bind的意义

std::bind 是一个强大的函数适配器,它位于 <functional> 头文件中。它的主要作用是将一个可调用对象(函数、函数指针、成员函数、函数对象、Lambda 表达式)与其参数进行绑定,生成一个新的可调用对象。一般用来减少传参或者调换参数顺序!

Q1:怎么理解bind的适配?

A1:就像电源适配器(充电头)一样,它能把墙上的交流电(一种形式)转换成手机需要的直流电(另一种形式)。std::bind 也是干这个的——它转换一个函数的形式。那怎么适配?很简单,bind它通过“固定”一些参数,把一个需要很多参数的函数,变成一个新函数。这个新函数需要的参数更少,或者参数的顺序变了,就达到了适配的效果

Q2:可调用对象是什么?

A2:这就是指任何能像函数一样后面加个 () 来调用的东西。它不只是普通的函数,还包括:

  • 普通函数: printfmyFunction

  • 函数指针: void (*fp)() = &myFunction;

  • 成员函数: MyClass::memberFunction (需要和对象一起用)

  • 函数对象 (Functors): 一个重载了 operator() 的类,看起来像函数。

  • Lambda 表达式: []() { /* ... */ }

Q3:生成一个新的可调用对象是什么意思?

A3:因为bind之后,函数的形式已经改变了,所以bind函数的返回值就是一个新的、行为被改变了的函数。你可以把它存到一个变量里(通常用 auto),然后像调用普通函数一样调用它。

bind意义总结:

想象一下,你要做一道菜,比如鱼香肉丝。正常的做法是:每次做的时候,你都要准备好肉丝、木耳、胡萝卜丝、调料等一系列食材,然后下锅炒。

std::bind 的作用就是:让你可以提前把一部分食材准备好,封装成一个“料理包”。下次你想吃的时候,只需要完成最后一步(比如下锅炒一下)就行了。

二:bind的用法

bind的第一个参数,必须填写你绑定的那个函数!

1:第一个参数

①:普通函数(函数指针)

void printSum(int a, int b) { std::cout << a + b; }
// 第一个参数是函数指针
auto boundFunc = std::bind(printSum, ...);

②:函数对象(Functor,重载了 operator() 的类)

struct Adder {
    int operator()(int a, int b) const { return a + b; }
};
Adder adder;
// 第一个参数是函数对象实例
auto boundFunc = std::bind(adder, ...);

③:Lambda 表达式

auto lambda = [](int x) { return x * x; };
// 第一个参数是Lambda表达式
auto boundFunc = std::bind(lambda, ...);

④:成员函数(Member Function Pointer)

class Logger {
public:
    void log(const std::string& msg) { std::cout << msg; }
};
Logger logger;
// 第一个参数是成员函数指针 &Logger::log
auto boundFunc = std::bind(&Logger::log, ...);

总结:

填写函数名,本质就是得到函数的地址,一般的函数名就等于与&函数名,但是成员函数不可以,成员函数没有这种隐式转化,所以要想绑定一个成员函数,必须进行&操作!这就是④与其他的区别!

2:其余参数

其余参数一般使用占位符来填写,用几个,则根据你想怎么设计bind之后形成的新函数!

占位符就是:std::placeholders::_1 ,std::placeholders::_2,std::placeholders::_3这种,当然你也可以写作_1,_2,_3这种,想使用后者需要展开命名空间:using namespace std::placeholders;

当然,我觉得使用std::placeholders::_1时最好的行为,因为在任何时候都不会发生命名冲突!

而bind减少传参或者调换参数顺序,就是这几个占位符起到的作用,

注:减少传承的参数也叫作绑定部分参数,更换参数的顺序也叫作重新排列参数顺序

举个例子:

a:重排参数顺序

我们的被绑定的函数printInfo是参数顺序是名字,年龄,分数,打印的语句也是如此,下面我们用bind更改这个函数的参数的顺序:

#include <iostream>
#include <functional>
using namespace std::placeholders; // 引入 _1, _2, _3

void printInfo(const std::string& name, int age, double score) {
    std::cout << name << " is " << age << " years old, score: " << score << std::endl;
}

int main() {
    // 原函数顺序: (name, age, score)

    // 1. 完全绑定:不需要占位符
    auto f1 = std::bind(printInfo, "Alice", 25, 99.5);
    f1(); // 输出: Alice is 25 years old, score: 99.5

    // 2. 使用占位符:改变参数顺序
    // 新函数要求参数顺序为: (分数, 年龄, 名字)
    auto f2 = std::bind(printInfo, _3, _2, _1); // _1<-score, _2<-age, _3<-name
    f2(88.5, 30, "Bob"); // 输出: Bob is 30 years old, score: 88.5

    return 0;
}

解释:

①:第一次使用bind我们没有对这个函数做任何事情,无法就是我们bind之后可以用f1函数达到和printInfo函数一样的效果

②:第二次使用bind我们用占位符更改了顺序,_3,_2,_1意味着,我们用这个函数传参的时候,比如f2(88.5, 30, "Bob");,此时的88.5明明是第一个参数,但是由于_1在最后一个参数,所以88.5会传入到score参数中,以此类推,所以占位符的数字就像是一个归属地,_1则代表第一个参数传入到_1,_2,_3也是如此

b:绑定部分参数

依旧是上面的例子,我们现在把某个参数写死,也就是本该填写占位符的参数换成了具体的参数,就能到达绑定部分参数的例子:

#include <iostream>
#include <functional>
using namespace std::placeholders; // 引入 _1, _2, _3

void printInfo(const std::string& name, int age, double score) {
	std::cout << name << " is " << age << " years old, score: " << score << std::endl;
}

int main() {
	// 原函数顺序: (name, age, score)


	// 3. 混合使用:固定某些参数,重排其他参数
	// 固定名字为"Charlie",新函数参数顺序为: (年龄, 分数)
	auto f3 = std::bind(printInfo, "Wtt", _1, _2); // _1<-score, _2<-age
	f3(22, 100); // 输出: Charlie is 22 years old, score: 95

	return 0;
}

运行结果:

解释:我们将参数写死为"Wtt"之后,后面我们就不用传名字这个参数了,只用传后两个,而占位符的顺序即使调换,也只会影响到后两个参数,和名字参数无关!

c:混合使用

在b的基础上,我们再调换一下顺序:

#include <iostream>
#include <functional>
using namespace std::placeholders; // 引入 _1, _2, _3

void printInfo(const std::string& name, int age, double score) {
	std::cout << name << " is " << age << " years old, score: " << score << std::endl;
}

int main() {
	// 原函数顺序: (name, age, score)


	// 3. 混合使用:固定某些参数,重排其他参数
	// 固定名字为"Charlie",新函数参数顺序为: (年龄, 分数)
	auto f3 = std::bind(printInfo, "Wtt", _2, _1); // _1<-score, _2<-age
	f3(22, 100); // 输出: Charlie is 22 years old, score: 95

	return 0;
}

解释:符合预期,调换占位符只会影响到没有写死的参数

三:bind绑定类的成员函数

bind中最常用的,最难的就去绑定一个类的成员函数,在第二大点的1小点中,我们知道只要绑定类的成员函数,则第一个参数必须&

此外,类的成员函数的第一个参数都是隐式的this指针,类似是this 指针ClassName*,所以我们想绑定一个类的成员函数的时候,我们的bind的第二个参数也必须写死,而不能使用占位符!

而bind去绑定类的成员函数的第二个参数的写法有几种:

我们就用一个简单的 Logger 类作为例子,想要bind绑定其中的log函数!

class Logger {
public:
    void log(const std::string& message) {
        std::cout << "Log: " << message << std::endl;
        count++;
    }
    int count = 0;
};

1:传递对象指针 (&obj

int main()
{
    Logger logger;
    auto func1 = std::bind(&Logger::log, &logger, _1);
    //                    成员函数地址   |对象指针|
    //                                  ^
    //                              第二个参数

    func1("Hello"); // 正确:调用 logger.log("Hello")


}

特点

  • 最常用的方式。

  • 要求 logger 对象必须持续存在,直到 func1 不再被使用。

  • 不会拷贝 Logger 对象。

2:传递对象副本 (obj

int main()
{
	Logger logger;
	auto func2 = std::bind(&Logger::log, logger, _1);
	//                               |对象副本|

	func2("Test"); // 注意:调用的是副本的 log 方法!

}

特点

  • 会调用 Logger 的拷贝构造函数,创建对象的完整副本。

  • 操作的是副本,对原对象 logger 没有任何影响。

  • 通常这是错误用法,除非你确实需要操作副本。

  • 如果类不可拷贝(如含有 unique_ptr 成员),会编译错误。

3:使用占位符延迟绑定 (_1

也可以不急着传bind的第二个参数,使用占位符,但是这种方法不常用

int main()
{
	Logger logger1, logger2;
	auto func3 = std::bind(&Logger::log, _1, _2);
	//                            |对象占位符| |参数占位符|

	// 调用时需要同时提供Logger对象和消息
	func3(&logger1, "From logger1"); // 调用 logger1.log("From logger1")
	func3(&logger2, "From logger2"); // 调用 logger2.log("From logger2")

}

特点

  • 极其灵活,可以动态决定对哪个对象操作。

  • 创建的是"通用"的绑定器。

  • 参数顺序:_1 对应对象,_2 对应成员函数的第一个参数,以此类推。

4:传递对象引用 

写法:使用 std::ref 包装对象
语义:按引用绑定,同样要求对象生命周期有效。

int main()
{
    Logger logger;
    auto func2 = std::bind(&Logger::log, std::ref(logger), _1);
    //                                |引用包装器|

    func2("World"); // 正确:调用 logger.log("World")

}

特点

  • 与传递指针效果类似,都是操作原始对象。

  • 在模板代码或需要统一接口时很有用。

  • 明确表示"我要按引用传递"的意图。

解释:当你的bind的第二个参数想传递对象引用的时候,必须使用ret引用包装器,切记不要std::bind(func, my_object&);,这是常见错误写法,&引用是类型修饰符,你不能修饰变量

四:bind的终极用法

bind在Linux中,用于形成回调函数是其最强大和最实用的应用场景!

类A中想使用类B的方法,所以类A的构造函数的参数就需要接收类B的方法,但是呢,我们可以用bind来把类A的构造函数中的用来接收类B的方法的参数写死,这样我们就不需要在麻烦的一次次传递参数了,其次类A中已经会写好了接收到类B的函数后,会怎么使用的代码!

简述如下:

  1. 类A 需要调用 类B 的方法

  2. 通过构造函数注入依赖(类B的方法)

  3. 使用 std::bind "写死"(固化)参数,简化调用

  4. 类A内部已经实现了如何使用这个注入的方法

具体例子如下:

#include <iostream>
#include <functional>

class Logger {
public:
	void log(const std::string& message, int severity) {
		std::cout << "[" << severity << "] " << message << std::endl;
	}
};

class Processor {
private:
	std::function<void(const std::string&, int)> logCallback;

public:
	// 构造函数接收一个复杂的回调函数
	Processor(std::function<void(const std::string&, int)> callback)
		: logCallback(callback) {}

	void process() {
		// 每次调用都需要传递所有参数
		logCallback("Processing started", 1);
		logCallback("Working...", 2);
		logCallback("Processing completed", 1);
	}
};

int main() {
	Logger logger;

	//使用bind绑定
	Processor processor(std::bind(&Logger::log, &logger, std::placeholders::_1, std::placeholders::_2));

	processor.process();
	return 0;
}

运行结果:

解释:我们对Processor的构造函数的参数进行了bind,这样我们之后创建Processor对象的时候,再也不需要手动的传递类logger的成员函数了!

分解解释

1. std::bind(&Logger::log, ...)

  • &Logger::log:获取 Logger 类的 log 成员函数的地址

  • 这是我们要绑定的目标函数

2. &logger

  • 第二个参数是调用成员函数所需的对象实例指针

  • std::bind 需要知道在哪个对象上调用这个成员函数

3. std::placeholders::_1 和 std::placeholders::_2

  • _1:表示第一个占位符,对应未来的第一个参数(msg

  • _2:表示第二个占位符,对应未来的第二个参数(severity

  • 这些占位符定义了参数如何传递给被绑定的函数

除开bind方法,不管你用哪种,你都不可能在Processor类中可以直接回调logCallback函数!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值