C++11:可调用对象

引言

可以调用对象是C++11引入的新概念,可以像函数调用方式的触发调用的对象就是可调用对象。其实可调用对象只是对C++98标准中一些调用方式进行了总结和扩展。

在C++98标准中,可被通过函数方式调用的对象有3个,他们分别是普通函数,函数指针,仿函数。C++11中增加了bind生成对象,lamdba表达式和function对象。所以C++11可调用对象总计6种,他们分别是普通函数,函数指针,(仿函数)函数对象,bind生成对象,lamdba表达式和function对象。

普通函数

普通函数就是普通意义上的函数,普通函数包括函数名,返回值,输入/出参数和一个函数体。例如:

bool isEven(int value)
{
    
    
	return (0 == value % 2);
}

std::vector<int> vec{
    
     2, 5, 9, 10, 11 };
auto res = std::find_if(vec.begin(), vec.end(), isEven);

isEven就是普通函数式的可调用对象。

函数指针

函数指针指指向普通函数的指针,一般可以理解为函数的引用,不同之处是函数指针可以多次绑定,即一个函数指针可指向多个普通函数。只要两者具有相同的函数签名格式(即有相同的输入/出参数,返回值)。例如:

bool isEven(int value)
{
    
    
	return (0 == value % 2);
}

using EvenDecide = bool(*)(int value);

std::vector<int> vec{
    
     2, 5, 9, 10, 11 };

EvenDecide evenDecide = isEven;

auto res = std::find_if(vec.begin(), vec.end(), evenDecide);

仿函数/函数对象

仿函数顾名思义就是类似函数的意义,一般意义上重写了operator()方法的类就是一个仿函数。仿函数包括模板类仿函数和普通类仿函数。仿函数的调用包括三种形式:通过构造临时对象调用operator(),通过对象隐式调用operator(),通过对象显示调用operator()。例如:

class EvenJudge
{
    
    
public:
	bool operator() (int value)
	{
    
    
		return (0 == value % 2);
	}
};

std::vector<int> vec{
    
     2, 5, 9, 10, 11 };

EvenJudge evenJudge;
// 1. 通过构造临时对象,调用operator()重载函数
auto res1 = std::find_if(vec.begin(), vec.end(), EvenJudge());
// 2. 通过对象,隐式调用调用operator()重载函数
auto res2 = std::find_if(vec.begin(), vec.end(), evenJudge);
// 3. 通过对象,显示调用operator()重载函数
auto res3 = std::find_if(vec.begin(), vec.end(), [&evenJudge](int value) {
    
    
	return evenJudge.operator()(value);
});

Lambda表达式

lambda表达式,是一个匿名函数(即没有函数名的函数)。Lambda表达式基于数学中的λ演算得名。lamdba表达式有输入/出参数,返回值,函数体,唯独没有函数名,所以lambda表达式不能通过类型名来显示声明对象。但是可以使用auto和类型推导。例如:

auto fnIsEven = [](int value) -> bool {
    
    
	return (0 == value % 2);
};

auto res4 = std::find_if(vec.begin(), vec.end(), fnIsEven);
auto res5 = std::find_if(vec.begin(), vec.end(), [](int value) {
    
    
	return(0 == value % 2);
});

Bind生成对象

bind是C++11新引入的模板,可匹配任意的可调用对象,包括函数指针,函数引用,成员函数指针,普通函数和lambda表达式。bind可以最多绑定9个参数。

根据绑定的参数个数和要绑定的调用对象类型,bind总共有10种不同的重载形式。在编译工程中编译器会根据绑定代码自动推导要使用的正确形式。

bind模板函数的第一个参数必须是一个可调用对象fun,可以是函数,函数指针,函数对象或成员函数指针,除第一个参数之外,bind最多还可以接收9个参数,而且这些参数的数量必须要和f的参数数量相等,因为这些参数最终都会作为f的入参。

bind模板函数的返回值是一个function对象,此对象的返回值会自动推导为可调用对象fun的返回值,具有operator()方法。当调用发生是function对象会把之前存储的入参传递给fun完成调用。

例如,一个函数形式为:func(a1, a2),经过bind将生成一个function对象bind(func, a1, a2),执行函数调用的方式为bind(func, a1, a2)()

除此之外,bind还支持占位符功能,占位符定义为_1,_2,_3一直到_9。占位符的意义在于它定义function对象的参数匹配。

例如:auto f = bind(func, _2, _1)表示定一个传参在f调用时需传第二个实参,第二个参数在f调用时需传递第一个实参。函数对象调用f(a1, a2) 等价于func(a2, a1)。

不仅如此,bind还支持多参数的分别绑定,例如:一个函数形式为:func(a1, a2),通过bind可产生第一个入参绑定的对象auto f = bind(func, a1),执行函数调用方式为f(a2)。通过这种方式可以把一个多参数的函数编程多个多参数函数对象的组合形式。可以完美的支持柯西规则。

bind的作用在于它可以将所有可调用对象进行归一化调用。大大的降低了可调用对象的差异性。可增加程序的可维护性和扩展性。

bind 普通函数

bind可绑定普通函数,生成function对象。

bool isEven(int value)
{
    
    
	return (0 == value % 2);
}

auto f = std::bind(isEven, std::placeholders::_1);
std::vector<int> vec{
    
     2, 5, 9, 10, 11 };
auto res6 = std::find_if(vec.begin(), vec.end(), f);

bind 函数指针

普通函数可以通过bind转换成function对象,函数指针依然可以。其实普通函数可以理解为:一个特殊的函数指针,一个const 引用类型的函数指针(一旦绑定永远不许修改)。

bool isEven(int value)
{
    
    
	return (0 == value % 2);
}

using EvenDecide = bool(*)(int value);
EvenDecide evenDecide = isEven;
auto f1 = std::bind(evenDecide, std::placeholders::_1);

std::vector<int> vec{
    
     2, 5, 9, 10, 11 };
auto res7 = std::find_if(vec.begin(), vec.end(), f1);

bind lambda表达式

lambda表达式就是一个匿名函数对象,因此也可以通过bind绑定转换为function对象。

auto fnIsEven = [](int value) -> bool {
    
    
	return (0 == value % 2);
};

auto f2 = std::bind(fnIsEven, std::placeholders::_1);

std::vector<int> vec{
    
     2, 5, 9, 10, 11 };
auto res8 = std::find_if(vec.begin(), vec.end(), f2);

bind 类成员函数

类成员函数是一类特殊的可调用对象,它的限制在于它需要和对象绑定。成员函数运行的环境是当前类对象。我们可以通过bind将当前对象绑定到function对象中。例如:如下代码段把evenJudge的operator(int)绑定成一个可调用对象f3。

class EvenJudge
{
    
    
public:
	bool operator() (int value)
	{
    
    
		return (0 == value % 2);
	}
};
EvenJudge evenJudge;

auto f3 = std::bind(&EvenJudge::operator(), evenJudge, std::placeholders::_1);
auto res9 = std::find_if(vec.begin(), vec.end(), f3);

bind 仿函数

bind不仅能绑定普通函数,函数指针,也能绑定任意的仿函数,包括标准库中定义的所有预定义仿函数对象。例如:

bind(std::greater<int>(), std::placeholders::_1, 10); // 检查x > 10
bind(std::plus<int>(), std::placeholders::_1, std::placeholders::_2); // 执行 x + y

关于仿函数绑定,有一点需要特殊说明的是,如果仿函数对象内部明确定义了result_type,bind可动推导返回类型,如果仿函数对象内部没有定义result_type,则需要在bind时明确指定返回类型。像

bind<result_type>(functor, ...);

举例:

class EvenJudge
{
    
    
public:
	bool operator() (int value)
	{
    
    
		return (0 == value % 2);
	}
};

auto f4 = std::bind<bool>(EvenJudge(), std::placeholders::_1);
auto res10 = std::find_if(vec.begin(), vec.end(), f4);

function对象

function是一个函数对象(仿函数)的容器,可以配合bind/lambda,存储bind/lambda表达式结果,使bind/lambda表达式可以多次被调用。同样function对象也是一个可调用对象。

总结

可以调用对象是C++11引入的新概念,指代此对象可以像函数调用方式的触发调用的对象。他们是普通函数,函数指针,(仿函数)函数对象,bind生成对象,lamdba表达式和function对象。

猜你喜欢

转载自blog.csdn.net/liuguang841118/article/details/124562842