以线程池举例
线程池需要接收要执行的任务,这些任务需要形成一个队列
任务可以是函数,lambda,重载括号运算符的类
那么在定义一个数组去保存这些任务该如何定义?
class my_thread {
using task_type = void(*)();//相当于typedef别名
my_queue<task_type> task_queue;
};
那这个队列可以存储函数指针来形成任务队列,且这些函数返回值为void不带参数
那对于重载了括号运算符的类如何放进去?
定义一个基类,任务继承该基类,运行时多态
struct task_base {
virtual ~task_base() = 0;//析构函数要定义为虚函数
virtual void run() const = 0;
};
// 用户编写的具体任务类
struct task_imple : public task_base {
void run() const override {
// 运算...
}
};
// 使用:
std::unique_ptr<task_base> pt = std::make_unique<task_imple>();
pt->run();
对于lambda函数,C++11的lambda有一个非常关键点要记住,编译器会给每一个lambda赋予独一无二的类型,没看错,是类型
auto lambda0 = []() { std::cout << "lambda0"; };
auto lambda1 = []() { std::cout << "lambda1"; };
这两个lambda的类型可以理解为都是编译器临时生成的,互不相同,不信你可以用std::is_same检查
所以my_queue<???> task_queue;
中问号处不知道写什么
using type0 = decltype(lambda0);
using type1 = decltype(lambda1);
static_assert(std::is_same<type0, type1>::value == false);
我们想要一个下面这种封装好的类
struct my_task {
template <typename F>
my_task(F&& f); // 模板化的构造函数,
// 从任意类型的函数对象构建任务
void operator()() const; // 没有显式的虚函数调用,
// 不用指针操作而用值语义
};
// 使用:
my_task t{ /* 任意的函数对象 */ }; // 用户不用填写函数对象的具体类型
// 由编译器推导
t();
std::function横空出世
C++语境下的类型擦除,技术上来说,是编写一个类,它提供模板的构造函数和功能
隐藏对象的具体类型,保留其行为
可以将函数,lambda以及重载了括号运算符的类再封装一层,使其功能一致,看不出区别了
也就是库作者把面向对象的代码写了,而不是推给用户写
struct task_base {
virtual ~task_base() {}
virtual void operator()() const = 0;
};
template <typename F>
struct task_model : public task_base {
F functor_;
template <typename U> // 构造函数是函数模板
task_model(U&& f) :
functor_(std::forward<U>(f)) {}
void operator()() const override {
functor_();
}
};
初始动机是用一个类型包装不同的函数对象。然后,考虑这些函数对象需要提供的功能(affordance),此处为使用括号运算符进行函数调用。
最后,把这个功能抽取为一个接口,此处为my_task,我们在在这一步擦除了对象具体的类型。
这便是类型擦除的本质:切割类型与其行为,使得不同的类型能用同一个接口提供功能。
以上参考https://zhuanlan.zhihu.com/p/351291649
整理以备快速拾起