一.function函数对象

1.什么是function函数对象

std::function 是 C++ 标准库提供的一个模板类(定义在 <functional> 头文件中)。它本质上是一个“函数包装器”或“可调用对象的容器”,我们可以将其理解成一个功能更强大,更安全的函数指针,它能够将我们函数/函数对象的类型保留下来。

2.如何使用function函数对象

借助function,我们可以对我们的函数/函数对象进行包装,并使用function定义的类来调用对应的函数/函数对象。例如我们想要使用function包装一个sum函数或者lambda函数对象,std::function<返回值(参数类型...)> 变量名(即需要使用函数类型实例化):

int sum(int a, int b)
{
	return a + b;
}
int main()
{
	function<int(int, int)>func1=sum;
	cout << func1(10, 20) << endl;
	function<int(int, int)>func2 = [](int a, int b)->int {return a + b; };
	cout<<func2(20, 30)<<endl;
}

这里的=也可以换成(),效果相同,分别调用了function类内部的赋值重载函数与函数调用运算符的重载函数,实际上这里要使用函数的地址,但是对于全局函数来说,编译器会自动的为全局函数加上&操作。

特别的,function也能包装一个成员方法:

class Sum
{
public:
	int sum(int a, int b)
	{
		return a + b;
	}
};
function<int(Sum*, int, int)>func3 = &Sum::sum;
Sum s;
cout<<func3(&s, 50, 50) << endl;
//或者使用临时对象
class Sum
{
public:
	int sum(int a, int b)const//const引用只能调用const方法
	{
		return a + b;
	}
};
int main()
{
	function<int(const Sum&, int, int)>func3 = &Sum::sum;
	cout << func3(Sum(), 50, 50) << endl;
}

由于普通成员方法依赖于对象,在函数的参数列表中会隐式的加上this指针,指向该对象,所以function定义时也需要加上对象指针类型,同时由于成员函数不会隐式的转化为指针,所以我们需要手动的取地址(当然还要加上成员函数的作用域),定义完成之后,再调用时,我们需要先传入一个对象的地址于定义时加上的对象指针类型对应。

3.模板的完全特例化与非完全(部分)特例化

为了理解function函数对象的底层实现原理,我们需要先了解模板的完全特例化与非完全(部分)特例化。这里我们举一个例子,首先完全特例化,我们存在一个Vector类,现在我们想要为char*这一个类型进行特例化操作,此时想要使用完全特例化

template<typename T>
class Vector
{
public:
Vector () { cout << "call Vector template init" << endl; }
};
// 下面这个是对char*类型提供的完全特例化版本
template<>
class Vector<char*>
{
public:
Vector () { cout << "call Vector<char*> init" << endl; }
};

之后,我们又发现对于指针类型,Vector类模板处理的存在问题,但是我们不清楚具体是哪一种类型,此时特例化就不再针对某一种具体的类型,而是针对一类相似的类型,此时我们就需要使用模板的非完全特例化

template<typename Ty>
class Vector<Ty*>
{
public:
Vector () { cout << "call Vector<Ty*> init" << endl; }

};

由此我们也可以总结出来编译器处理特例化的顺序,即原模版>完全特例化>非完全特例化。

模板的实参推演

我们还需要深入的理解一下模板的实参推演,我们想要推演复杂的类型时该怎么办,例如:我们推演一个函数类型:



template<typename T> 
void func (T a)
{
    cout << typeid (T) .name () << endl;
}
int sum(int a, int b) { return a + b; }
int main()
{
    func(sum);
}

这样虽然编译器能够自动的推演出T的类型为(int (__cdecl*)(int,int)),是一个函数指针类型(这里的函数传入后会隐式的转化为函数指针),但是这却将函数类型的所以信息都杂糅到一起了,实际上使用起来很不方便,所以我们想要将其类型分割开来:

template<typename R, typename A1, typename A2>
void func2(R(*a) (A1, A2))
{
    cout << typeid (R).name() << endl;
    cout << typeid (A1).name() << endl;
    cout << typeid (A2).name() << endl;

}
int main()
{
    func2(sum);
}

4.function函数对象的底层原理

function函数对象的底层实现借助于一个模板类,function函数对象之所以能够调用函数,核心在于其类中实现的operator()()重载函数。

二.lambda表达式

我们可以将lambda表达式看成函数对象的升级版,对于前面提到的函数对象,其存在着一些不便,例如:我们要自定义一个优先级队列,那么我们需要一个函数对象作为模板参数传递,但是如果单单使用函数对象,我们就不得不再定义一个类,但这个类我们可能就在这里使用到了,而对于一个一次性的操作来说,我们定义一个类显然不太好,所以就需要使用到我们的lambda表达式。

实际上lambda的底层原理就是编译器生成的一个匿名的函数对象,我们可以将其看成一个一次性的函数对象。

语法:[捕获列表] (参数列表) mutable -> 返回值类型 { 函数体 }

如上图是语法个部分的解释:

我们这里重点看捕获列表

[]:表示不捕获任何外部变量
[=]:以传值的方式捕获外部的所有变量
[&]:以传引用的方式捕获外部的所有变量
[this]:捕获外部的this指针

[=,&a]:以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获

[a,b]:以值传递的方式捕获外部变量a和b
[a,&b]:a以值传递捕获,b以传引用的方式捕获

全局变量和静态变量(static)不需要捕获,在 Lambda 内部可以直接使用。因此,如果 Lambda 定义在全局作用域,捕获列表必须为空 []

三.bind绑定器

bind绑定器返回的结果就是一个函数对象,bind底层就是一个函数模板。

它的核心作用是接收一个现有的可调用对象(普通函数、成员函数、函数对象等),提前固定住它的一部分参数,或者重新排列参数的顺序,然后生成一个新的、参数更简单或更贴合需求的函数对象。

例如:我们可以使用bind绑定器进行函数/函数对象的参数绑定调用:

int sum(int a, int b) { return a + b; }
class Test
{
public:
int sum(int a, int b) { return a+b;}

};

int main ()
{
// bind是函数模板 可以自动推演模板类型参数
cout << bind(sum, 10, 20) () << endl;
cout << bind (&Test :: sum, Test(), 20, 30)() << endl;
}

但是bind绑定器的核心在于其占位符,占位符定义在 std::placeholders,最多支持占位20个,在使用前,必须包含 <functional> 头文件,并且通常会加上 using namespace std::placeholders; 来简化代码,例如:

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

int main()
{
	cout << bind(sum, _1,20)(10) << endl;
}

借助function函数对象与bind绑定器实现的简单线程池

class Thread
{
public:
	Thread(std::function<void()>func):_func(func){}
	std::thread start()
	{
		std::thread t(_func);
		return t;
	}
private:
	std::function<void()>_func;
};
class ThreadPool
{
public:
	ThreadPool() { }
	~ThreadPool() 
	{ 
		for (int i = 0; i < _pool.size(); ++i)
		{
			delete _pool[i];
		}
	}
	void startPool(int size)
	{
		for (int i = 0; i < size; ++i)
		{
			_pool.push_back(new Thread(std::bind(&ThreadPool::runInThread, this, i)));
		}

		for (int i = 0; i < size; ++i)
		{
			_thread.push_back(_pool[i]->start());
		}
		for (auto& t:_thread)
		{
			t.join();
		}
	}
private:
	std::vector<Thread*>_pool;
	std::vector<std::thread>_thread;
	void runInThread(int id)
	{
		cout << "call runInThread id:" << id << endl;
	}
};

int main()
{
	ThreadPool pool;
	pool.startPool(10);

}

当然,这里只是实现了一个简单的线程池,并没有处理线程安全问题。

更多推荐