C++11新特性——std::bind参数绑定

系列文章目录

C++11新特性大全+实例

文章目录

  • 系列文章目录
  • 前言
  • 一、std::bind概述
  • 1.std::bind简介
  • 2.std::bind原型
  • 二、使用std::bind绑定
  • 1.绑定第一类
  • 2.绑定第二类
  • 3.绑定第三类
  • 三、占位符
  • 总结


前言

C++ 这门编程语言的历史可以追溯至 1979 年,当时的 Bjarne Stroustrup(C++ 之父,后续简称 Stroustrup)还在使用 Simula 语言进行开发工作。

1998 年,C++ 标准委员会发布了第一版 C++ 标准,并将其命名为 C++ 98 标准。据不知名人士透露,《带注释的C++参考手册》这本书对 C++ 98 标准的制定产生了很大的影响。

经过作者的不断迭代,一本书往往会先后发布很多个版本,其中每个新版本都是对前一个版本的修正和更新。C++ 编程语言的发展也是如此。截止到目前(2020 年),C++ 的发展历经了以下 3 个个标准:

2011 年,新的 C++ 11 标准诞生,用于取代 C++ 98 标准。

2014 年,C++ 14 标准发布,该标准库对 C++ 11 标准库做了更优的修改和更新;

2017 年底,C++ 17 标准正式颁布;

虽然学习 C++11 需要花些时间,但这是非常值得的;C++11 非常实用,它不但提高了开发效率,还让程序更加健壮和优雅。程序员应该乐于升级换代已有的知识,而学习和使用 C++11 早就是大势所趋。

|版本声明:山河君,未经博主允许,禁止转载

一、std::bind概述

1.std::bind简介

说起bind函数,应该有不少人第一反应是TCP/IP编程中用于将套接字和本地地址进行绑定的用法。而本文说的bind同样是用于绑定。但是是用于将已有变量绑定到可调用函数的参数上。

早在C98中,就有两个函数bind1st和bind2nd,他们被用于绑定已有变量到可执行函数的第一个参数和第二个参数,相同的是他们都只能绑定一个参数。

如果看到这还是不太明白,我们直接看例子,假如现在想遍历一个容器中小于5的值

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">void fun(int i = 0, int j = 0)
{
	if (i < j)
		cout << i << endl;
}

int main(int argv, char* argc[])
{
	vector<int> vecArray = { 1,2,3,4,5,6,7,8,9,10 };
	for_each(vecArray.begin(), vecArray.end(), bind2st(fun, 5));
	return 0;
}
</code></span></span>

此时5就被绑定到fun函数的第二个参数上了。但是bind1st和bind2st局限性太大,所以C++11中推出了std::bind。

2.std::bind原型

直接跟进bind函数,发现它实际为两个模板函数

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">// FUNCTION TEMPLATE bind (implicit return type)
template <class _Fx, class... _Types>
_NODISCARD _CONSTEXPR20 _Binder<_Unforced, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args) {
    return _Binder<_Unforced, _Fx, _Types...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}

// FUNCTION TEMPLATE bind (explicit return type)
template <class _Ret, class _Fx, class... _Types>
_NODISCARD _CONSTEXPR20 _Binder<_Ret, _Fx, _Types...> bind(_Fx&& _Func, _Types&&... _Args) {
    return _Binder<_Ret, _Fx, _Types...>(_STD forward<_Fx>(_Func), _STD forward<_Types>(_Args)...);
}
</code></span></span>

它实际上是使用一个可调用对象,通过绑定一些参数生成一个新的可调用对象。这里注意一下细节,说的是可调用对象而不是可调用接口函数

std::bind绑定的可调用对象:

可调用对象
普通函数、静态函数、模板函数
类成员函数、类成员静态函数、类成员模板函数、类成员变量、类内部绑定
仿函数、Lambda表达式

二、使用std::bind绑定

1.绑定第一类

普通函数、静态函数、模板函数
输入两个数,打印两个数中较大的一个

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">void fun1(int i, int j){ cout << (i > j ? i : j) << endl; } //普通函数

static void fun2(int i, int j) { cout << (i > j ? i : j) << endl; } //静态函数

template <typename T1 = int, typename T2 = int>
void fun3(T1 i, T2 j) { cout << (i > j ? i : j) << endl; } //模板函数

int main()
{
    auto fun1Test = bind(fun1, 1, 2);
    auto fun2Test = bind(fun2, 1, 2);
    auto fun3Test = bind(fun3<>, 1, 2);

    fun1Test();
    fun2Test();
    fun3Test();

    return 0;
}
</code></span></span>

毫无疑问结果都为2.

2.绑定第二类

类成员函数、类成员静态函数、类成员模板函数、类成员变量、类内部绑定

这部分比较重要的就是要明白普通函数和类成员函数有什么区别,我们都知道的是在函数指针上面,类成员函数指针不仅要指定目标函数的形参列表和返回类型,还必须指出成员函数所属的类

当然类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。

所以我们在bind类成员函数时,需要显示的传入类对象、或者类对象的引用、或者类对象的指针
如下:

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">class Test
{
public:
    void fun1(int i, int j) { cout << (i > j ? i : j) << endl; } //类成员普通函数

    static void fun2(int i, int j) { cout << (i > j ? i : j) << endl; } //类静态函数

    template <typename T1 = int, typename T2 = int>
    void fun3(T1 i, T2 j) { cout << (i > j ? i : j) << endl; } //类模板函数

    void fun4() { auto fun1Test = bind(&Test::fun1, this, 1, 2); fun1Test(); }; //类内部使用

public:
    int m_X = 10;
};


int main()
{
    Test test;
    Test* pTest = &test;
    auto fun1Test = bind(&Test::fun1, test, 1, 2); //传入类对象
    auto fun2Test = bind(Test::fun2, 1, 2);	//因为静态函数,所以无需传入类
    auto fun3Test = bind(&Test::fun3<>, &test, 1, 2); //传入类引用
    auto memberVar = std::bind(&Test::m_X, pTest); //传入类对象指针

    fun1Test();
    fun2Test();
    fun3Test();
    test.fun4(); //类内部使用,直接使用this
    cout << memberVar() << endl;

    return 0;
}
</code></span></span>

3.绑定第三类

仿函数、Lambda表达式

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">class Compare {
public:
    void operator()(int i, int j) { cout << (i > j ? i : j) << endl; }
};

int main()
{
    auto fun1 = bind(Compare(), 1, 2); //仿函数自动实例化
    auto fun2 = bind([](int i, int j) {cout << (i > j ? i : j) << endl; }, 1, 2); //匿名函数
    fun1();
    fun2();
    return 0;
}
</code></span></span>

三、占位符

占位符是用于表明要绑定的参数在生成新的可执行对象的位置。有的时候我们不需要立马进行绑定,或者不需要全部进行绑定,甚至是需要改变参数传入的顺序时候使用

<span style="color:#333333"><span style="background-color:#f9f5e9"><code class="language-cpp">
void fun(int i, int j = 2) { cout << (i > j ? i : j) << endl; }

int main()
{
    auto fun1 = bind(fun, 1, std::placeholders::_1);
    auto fun2 = bind(fun, std::placeholders::_1, 2); //第二个参数必须输入,这个时候不识别默认参数
    auto fun3 = bind(fun, std::placeholders::_2, std::placeholders::_1);
    fun1(2);
    fun2(1);
    fun3(1, 2); //此时1作为第二个参数输入,2作为第一个参数输入
    return 0;
}
</code></span></span>

<think>好的,我现在需要帮助用户理解如何使用C++中的std::bind绑定成员函数。首先,我得回忆一下std::bind的基本用法和相关知识。根据用户提供的引用内容,特别是引用[3]和引用[4],里面提到了使用std::function和std::bind来包装类成员函数的例子。 首先,成员函数和普通函数不同,因为它们的第一个参数隐式地是类的实例指针(即this指针)。所以当用std::bind绑定成员函数时,需要明确指定该实例对象。用户的问题是如何绑定成员函数,可能需要传递一个对象实例作为第一个参数,或者使用占位符来延迟绑定。 接下来,查看引用[3]中的示例代码,其中有一个例子是将Plus类的成员函数plus_d绑定std::function对象上。代码中使用了std::bind(&Plus::plus_d, Plus(), _1, _2)。这里,第一个参数是成员函数的指针,第二个参数是Plus类的一个实例,后面是两个占位符,对应成员函数plus_d的参数。这说明在绑定成员函数时,需要提供对象实例作为第二个参数std::bind,或者在调用时传入。 那问题来了,如果用户想要在绑定时固定对象实例,或者希望在调用时再传入对象?比如,如果使用占位符来代表对象实例,但这样可能不太常见,因为成员函数的第一个参数是隐式的。所以正确的做法可能是在std::bind的时候就将对象实例绑定进去,或者通过占位符在调用时传入。但根据C++的规则,成员函数需要通过对象来调用,所以通常需要在bind的时候绑定对象实例,或者传递指针/引用。 再看引用[4],其中提到std::bind的实现机制和主要作用,可能更详细地解释了参数绑定的顺序。成员函数的指针需要和对象实例结合,之后才是成员函数的参数。所以在语法上,std::bind的第一个参数是成员函数指针,第二个是对象实例,然后才是成员函数所需的参数,可以使用占位符。 用户可能需要一个具体的例子,比如如何将一个类的成员函数注册为另一个类的回调函数,如引用[1]和引用[2]提到的场景。例如,类A有一个成员函数,需要被类B作为回调使用。这时,需要使用std::bind将类A的实例和成员函数绑定,生成一个可调用对象,然后传递给类B。 总结步骤应该是:1. 包含必要的头文件,如<functional>;2. 使用&ClassName::MemberFunction获取成员函数指针;3. 在std::bind中传递对象实例(可以是临时对象、指针或引用);4. 使用占位符_1, _2等表示调用时的参数;5. 将生成的bind对象赋值给std::function以便存储和调用。 需要注意的是,当绑定的对象实例是临时对象时,比如通过值传递,如Plus(),那么这个对象的生命周期需要被考虑,否则可能导致悬垂引用。如果对象是通过指针或引用传递的,用户需要确保在回调时对象仍然有效。 可能还需要解释占位符的位置,比如成员函数如果有两个参数,那么std::bind中的占位符从_1开始,因为对象实例已经绑定,剩下的参数对应成员函数的参数。 最后,根据用户提供的示例代码,应该构造一个类似的例子,展示如何正确使用std::bind绑定成员函数,并可能指出常见错误,比如忘记传递对象实例或占位符顺序错误。</think>### 使用 `std::bind` 绑定 C++ 成员函数 #### 核心步骤 1. **包含头文件**:确保包含 `<functional>` 头文件。 2. **语法规则**:成员函数指针需与对象实例绑定参数通过占位符或固定值指定。 3. **绑定方式**: - 若需绑定特定对象实例(如固定对象),需在 `std::bind` 中显式传递。 - 若需延迟绑定对象(如调用时传入),需使用占位符 `_1` 表示对象参数。 #### 示例代码 ```cpp #include <iostream> #include <functional> class Calculator { public: double multiply(double a, double b) { return a * b; } }; int main() { Calculator calc; // 绑定成员函数:固定对象实例,参数通过占位符传递 std::function<double(double, double)> func = std::bind(&Calculator::multiply, &calc, std::placeholders::_1, std::placeholders::_2); std::cout << "3 * 4 = " << func(3, 4) << std::endl; // 输出 12 return 0; } ``` #### 代码解析 - **`std::bind` 参数顺序**: 1. **成员函数指针**:`&Calculator::multiply`。 2. **对象实例**:`&calc`(此处传递指针,确保对象生命周期有效)。 3. **成员函数参数**:使用占位符 `_1` 和 `_2` 表示调用时的输入参数[^3][^4]。 - **注意事项**: - 若对象实例为临时对象(如 `std::bind(..., Calculator(), ...)`),需注意其生命周期。 - 占位符 `_1` 对应调用时的第一个参数,与成员函数参数顺序一致。 #### 扩展场景:延迟绑定对象实例 ```cpp // 定义可调用对象,要求调用时传入 Calculator 实例 std::function<double(Calculator*, double, double)> delayed_func = std::bind(&Calculator::multiply, std::placeholders::_1, // 对象实例占位符 std::placeholders::_2, // 第一个参数 std::placeholders::_3); // 第二个参数 Calculator calc2; std::cout << "5 * 6 = " << delayed_func(&calc2, 5, 6) << std::endl; // 输出 30 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值