序言
当我们学 C 语言 时,当讨论到可调用对象时,第一时间会想到函数,或者是函数指针。但是经过 C++ 的学习,可调用对象可就多了,除了上述两个还包括仿函数,lambda 表达式,类成员函数等。怎么进行统一的管理呢?
1. function 包装器
1.1 function 的作用
很多时候,我们的可调用对象可能大相径庭,但是他们的功能都是一样的,如下代码:
// 函数
int AddFunc(const int& left, const int& right)
{
return left + right;
}
// 仿函数
struct Addobject
{
int operator()(const int& left, const int& right)
{
return left + right;
}
};
// lambda 表达式
auto AddLam = [](const int& left, const int& right) { return left + right; };
我们就可以使用 function
将功能相同,类型不同的可调用对象统一起来!并且在大多数场景下,function
都是可以平替掉 函数指针
的,因为他更加的灵活。
1.2 function 的使用
function
使用起来也十分的方便,我们首先来看他的声明:
std::function
template <class T> function; // undefined
template <class Ret, class... Args> class function<Ret(Args...)>;
在这里我们主要介绍第二种形式:
Ret
:代表调用对象的返回值class... Args
:代表可调用对象的参数
我们现在就针对于可调用对象分别做使用演示:
1. 函数
// 函数
int AddFunc(const int& left, const int& right)
{
return left + right;
}
int main()
{
// 注意匹配返回值类型和参数类型
std::function<int(int, int)> func1 = AddFunc;
std::cout << func1(1, 2) << std::endl;
return 0;
}
2. Lambda 表达式
int main()
{
// lambda 表达式
std::function<int(int, int)> func1 = [](const int& left, const int& right) { return left + right; };
std::cout << func1(1, 2) << std::endl;
return 0;
}
使用时直接使用 function
接受表达式返回的类型。
3. 仿函数
// 仿函数
struct Addobject
{
int operator()(const int& left, const int& right)
{
return left + right;
}
};
int main()
{
std::function<int(int, int)> func1 = Addobject();
std::cout << func1(1, 2) << std::endl;
return 0;
}
4. 类成员函数
这个就较为特殊了,类成员函数分为 普通成员函数,静态成员函数
,在这里我们首先介绍前者。
这里可就有坑了!我们先看看怎么入坑的,以及为什么有坑?
参数类型和数量看起来是匹配的,其实不然,大家别忘了一个隐含指针:
这样的话参数列表就和我们的 function
不匹配了,有什么好的解决方案吗?这里有两个:
// 方案一: 创建一个类的对象,传入指针
std::function<int(AddClass*, int, int)> func1 = &AddClass::AddFunc;
AddClass ac;
std::cout << func1(&ac, 1, 1) << std::endl;
可以看出来,这十分的不优雅,太挫了!并且和上面的 function
一比,参数都变了!
我们再来看一个比较优雅的:
// 方案二:使用 lambda 表达式作为中间中转一下
std::function<int(int, int)> func1 = [](const int &left, const int &right)
{
return AddClass().AddFunc(left, right);
};
这个看起来就顺眼多了吧!
起始还有一个方案三,我们在介绍 bind
之后再介绍一下。
现在我们再介绍 静态成员函数,这个就友好一点了,这是因为静态成员函数 不依赖于类的任何特定实例来执行其功能
,因此不需要 this
指针来访问类的成员变量或成员函数。
举个例子吧:
class AddClass
{
public:
int AddFunc(const int& left, const int& right)
{
return left + right;
}
static int AddStatic(const int& left, const int& right)
{
return left + right;
}
};
int main()
{
std::function<int(int, int)> func1 = AddClass::AddStatic;
std::cout << func1(1, 2) << std::endl;
return 0;
}
5. 模板函数
模板函数再被介绍之前,需要指定类型
:
template<class T>
T AddTle(const T& left, const T& right)
{
return left + right;
}
int main()
{
std::function<int(int, int)> func1 = AddTle<int>;
std::cout << func1(1, 2) << std::endl;
return 0;
}
2. bind 适配器
2.1 bind 的作用
C++11 中的 std::bind
是一个功能强大的函数适配器,它用于 生成新的可调用实体(通常是函数对象)
。std::bind
可以 绑定函数调用的某些参数到特定的值
,然后返回一个新的函数对象,这个新的函数对象在被调用时,会自动使用绑定的参数值,并接受其他未绑定的参数。
我们在这里主要介绍他的两个主要功能 顺序互换
以及 参数绑定
。
2.2 顺序互换
这个功能使用的比较少,但是我们在这里简单介绍一下。首先我们创建一个进行除法操作的的函数:
double my_divide(double x, double y) { return x / y; }
int main()
{
std::cout << my_divide(10, 1) << std::endl; // 10
return 0;
}
现在我想要使用 bind
将被除数和除数的顺序颠倒一下,形成一个新的调用对象,先看一下该适配器的声明:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
第一个参数是传入可调用对象,之后便是模板参数包,他的使用说明为:
Each argument may either be bound to a value or be a placeholder:
- If bound to a value, calling the returned function object will always use that value as argument.
- If a placeholder, calling the returned function object forwards an argument passed to the call (the one whose order number is specified by the placeholder).
想表达的意思主要是:
- 如果传入的参数为一个固定的值,则可调用对象就会一直使用那个值
- 如果是一个占位符,则可调用对象使用传入的参数
这里的占位符位于:std::placeholders
来代表一个参数。
我们来看一下,如果使用 bind
来创建一个和函数功能一模一样的可调用对象:
double my_divide(double x, double y) { return x / y; }
int main()
{
std::cout << my_divide(10, 1) << std::endl; // 10
auto func = std::bind(my_divide, std::placeholders::_1, std::placeholders::_2);
return 0;
}
首先我们传入了函数地址(函数名),之后我们想要在调用时手动输入参数,于是按照顺序传入占位符 1,占位符 2。
现在我们想要调换一下参数调用顺序,于是我们直接将占位符(一个占位符对应该位置的参数
)的顺序调换 :
auto func = std::bind(my_divide, std::placeholders::_2, std::placeholders::_1);
这就完成啦,当我们调用 func(10, 1)
时结果就会变成 0.1
。
2.3 绑定参数
这个功能就比较强大了,明明有些函数我们需要传递 3 个参数,但通过绑定,我们可以直接传 2 个,1 个,甚至不传,就比如,还是拿上一个函数举例:
double my_divide(double x, double y) { return x / y; }
int main()
{
std::cout << my_divide(10, 1) << std::endl; // 普通调用
auto func1 = std::bind(my_divide, 20, std::placeholders::_1); // 绑定一个参数
std::cout << func1(10) << std::endl;
auto func2 = std::bind(my_divide, 20, 10); // 绑定两个参数
std::cout << func2() << std::endl;
return 0;
}
还记得我们上面的方案三吗,现在我们再次实现:
class AddClass
{
public:
int AddFunc(const int& left, const int& right)
{
return left + right;
}
};
int main()
{
std::function<int(int, int)> func1 = std::bind(&AddClass::AddFunc, AddClass(), std::placeholders::_1, std::placeholders::_2);
std::cout << func1(1, 2) << std::endl;
return 0;
}
大家可能现在觉得可能这个感觉也没啥呀?没感觉有啥用呀?其实是因为我们没有对应的使用场景,没有代入感!还记得在学习多线程时一个函数吗?
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
这个函数我们主要关注后两个参数,前者代表我们需要调用的函数,后者是参数。平常使用感觉按部就班就好,但是万一我们是调用一个类中的函数呢?类中的普通成员是带有 this
指针的,和后者的参数不匹配的。之前,我们的解决方案是封装几层调用关系;现在的话,一个 bind
就能解决了!
3. 序言
在这篇文章中,我们主要介绍了 function 包装器以及 bind 适配器
,现在我们对可调用对象又有了一个新的认识,希望大家有所收获!