跟我学c++中级篇——类型擦除的应用
创始人
2024-05-03 19:46:48
0

一、类型擦除的效果

前面分析了类型擦除,通过分析可以知道。类型擦除其实就是一种抽象,不通过继承来实现动态行为,从而更好的实现可扩展性和耦合性。在设计程序时,大家都知道,要依赖于抽象而不是具体。由于类型的限制,在c++这种强类型语言中,往往会造成抽象的复杂性,甚至在某些情况下是无法达到抽象的结果的。
特别在模板编程中,这种现象往往更是突出,所以类型擦除在这方面有着很大的施展空间。

二、例程及分析

在这里首先分析一个线程任务的队列控制例程,通过这个例程可以更好的明白类型擦除的用处。一般来说,任务就是一个函数,或者可以理解成一个实现函数功能的对象。通过前面的学习,就可以知道了,主要有三大类,普通函数(含函数指针)、仿函数和lambda表达式。在摒除一些细节后,如何把这三类统一起来就是一种抽象。在早期的开发中,一般都是只考虑一类,大多都是使用函数指针,在c++11推出以后,更多的开发者开始使用std::function,因为它把三类也给统一了。下面看一个简单的例程,形象的理解一下:

#include 
#include 
#include //基础应用
typedef void(*pFtask)();
void WorkTask()
{std::cout << "do work" << std::endl;
}
class BaseTask
{
public:BaseTask() = default;virtual ~BaseTask() = default;
public:virtual void DoWork() const = 0;
};
class TaskImpl :public BaseTask
{
public:TaskImpl() = default;~TaskImpl() = default;
public:void DoWork()const override{WorkTask();}
};
class ThreadWork
{
public:ThreadWork() = default;~ThreadWork() = default;
public:
public:std::queue> qTask_;
};
int main()
{//make_unique是c++14才提供,这里暂时用主线程来模拟线程std::unique_ptr pTask = std::make_unique();ThreadWork tw;tw.qTask_.emplace(std::move(pTask));//仍然使用主线程模拟任务执行auto pT = std::move(tw.qTask_.front());tw.qTask_.pop();pT->DoWork();system("pause");
}

下面就从上面这个最初的代码一步步的扩展开去,看看类型擦除在这上面是如何应用的。上面的代码其实对于一些内部使用的线程操作基本没有什么问题了,再完善一下队列,增加一个内存池,这就是一个基本可用的线程池的整体模型。
但是这样有一些问题,第一,每次实现不同的任务或者不同的开发者实现自己的任务都需要继承基础的抽象类,这里先不谈效率,时间长后,对维护本身就非常不友好;第二,使用者无法屏蔽对代码的抽象,仍然需要了解代码,即使这个代码很简单,同样这种抽象也限制了任务的扩展。
另外,如果想使用仿函数和lambda表达式,又该怎么样?能不能有一种情况,让客户拿来任务数据结构直接就使用,只实现任务函数即类似于下面的这样:

//定义
class WorkTask
{
public://函数对象管理template WorkTask(F&& f):f_(std::move(f));
public:void operator()()const;
private:F f_;
};//使用
WorkTask wTask{/*用户自定义的任务执行函数对象,要支持函数指针、lambda表达式和仿函数*/};
wTask();

如果这样使用,对应用用户就友好了很多,编程的维护复杂性也大大降低。怎么实现这个呢?先看一下代码:

class BaseTask
{
public:BaseTask() = default;virtual ~BaseTask() = default;
public:virtual void DoWork() const = 0;virtual void operator()()const = 0;
};
template
class TaskImpl :public BaseTask
{
public:TaskImpl() = default;templateTaskImpl(T&& t) :func_(std::forward(t)) {}~TaskImpl() = default;
public:void DoWork()const override{//WorkTask();std::cout << "start task!" << std::endl;}void operator()()const override{func_();}public:F func_;
};

在上面的代码中,首先增加了对小括号的重载,重载的目的当然就是对仿函数的支持。在前面的“类型擦除”分析中,可以知道,函数指针和受约束的模板构造函数是实现类型擦除的关键。从上面的代码看,抛除对约束的控制,函数指针(包含仿函数)就是首要(func_),基本的任务体TaskImpl采用了模板类和模板构造函数。好多技术在学习的过程中,往往是只重点对某一方向进行分析,但较少的是前后响应,互相借鉴,这并不是说写文章的人水平不够,而是说很多写文章的人往往忽视了这一点,陷入了技术细节的分析和描述。而这也往往是很多人学会一个单独的技巧或者技术后不能够应用于实际场景的一个非常重要的原因。扯远了,再扯回来。
上面的代码解决了类型擦除的基本问题,也印证了前面说的如何实现类型擦除的手段。但是这样用,仍然有一些暴露细节,所以需要再封装一层:

class TaskWrapper
{
public:TaskWrapper() = default;templateTaskWrapper(F &&f){using standType =  TaskImpl;pTask_ = std::make_unique(std::forward(f));}~TaskWrapper() = default;
public:void operator()()const {pTask_->operator()();//pTask_->DoWork();}
public:std::unique_ptr pTask_;
};

通过对基本任务类的封装,并且进行仿函数的operator重载,这样,就可以将三类函数对象的支持统一到此类中。对其进行测试:

void WorkTask()
{std::cout << "do work,type erase" << std::endl;
}class WorkTaskFunc
{
public:void operator()()const {std::cout << "functor  ,type erase" << std::endl;}
};auto tasklambda = []() {std::cout << "lambda,type erase" << std::endl; };
int main()
{//分三种情况//普通函数TaskWrapper tw1{ WorkTask };tw1();//仿函数TaskWrapper tw2{ WorkTaskFunc{} };tw2();//Lambda表达式TaskWrapper tw3{ tasklambda };tw3();return 0;
}

然后再象开始一样把代码集成到ThreadWork中:

void TestTask()
{//分三种情况//普通函数TaskWrapper tw1{ WorkTask };tw1();//仿函数TaskWrapper tw2{ WorkTaskFunc{} };tw2();//Lambda表达式TaskWrapper tw3{ tasklambda };tw3();ThreadWork tWork;tWork.qW_.emplace(std::move(tw1));tWork.qW_.emplace(std::move(tw2));tWork.qW_.emplace(std::move(tw3));auto t1 = std::move(tWork.qW_.front());tWork.qW_.pop();auto t2 = std::move(tWork.qW_.front());tWork.qW_.pop();auto t3 = std::move(tWork.qW_.front());tWork.qW_.pop();t1();t2();t3();
}

程序一运行就崩溃了,报得错误是在执行t1()时,资源被释放,TaskWrapper中的pTask_已经被释放。原来此处用的是std::unique_ptr,它只能移动不能拷贝,于是就得对移动构造函数和移动复制函数进行显示声明,同时处理掉复制相关的函数:

class TaskWrapper
{
......
public:TaskWrapper(TaskWrapper&& other)noexcept :pTask_(std::move(other.pTask_)) {}TaskWrapper& operator = (TaskWrapper&& rhs)noexcept{pTask_ = std::move(rhs.pTask_);return *this;}//处理复制构造、赋值函数TaskWrapper(const TaskWrapper&) = delete;TaskWrapper& operator=(const TaskWrapper&) = delete;
......
};

最后再统一到队列中,看完整的代码:

#include 
#include 
#include 
#include //基础应用
typedef void(*pFtask)();
//普通函数
void WorkTask()
{std::cout << "do work,type erase" << std::endl;
}
//仿函数
class WorkTaskFunc
{
public:void operator()()const {std::cout << "functor  ,type erase" << std::endl;}
};
//lambda表达式
auto tasklambda = []() {std::cout << "lambda,type erase" << std::endl; };//任务基础抽象类
class BaseTask
{
public:BaseTask() = default;virtual ~BaseTask() = default;
public:virtual void DoWork() const = 0;virtual void operator()()const = 0;
};
//标准任务类
template
class TaskImpl :public BaseTask
{
public:TaskImpl() = default;templateTaskImpl(T&& t) :func_(std::forward(t)) {}~TaskImpl() = default;
public:void DoWork()const override{//WorkTask();std::cout << "start task!" << std::endl;}void operator()()const override{func_();}public:F func_;
};
//任务封装打包器
class TaskWrapper
{
public:TaskWrapper() = default;templateTaskWrapper(F &&f){using standType =  TaskImpl;//typedef     TaskImpl  standType;pTask_ = std::make_unique(std::forward(f));}~TaskWrapper() = default;
public:TaskWrapper(TaskWrapper&& other)noexcept :pTask_(std::move(other.pTask_)) {}TaskWrapper& operator = (TaskWrapper&& rhs)noexcept{pTask_ = std::move(rhs.pTask_);return *this;}TaskWrapper(const TaskWrapper&) = delete;TaskWrapper& operator=(const TaskWrapper&) = delete;
public:void operator()()const {pTask_->operator()();//pTask_->DoWork();}
public:std::unique_ptr pTask_;
};
//线程任务管理类
class ThreadWork
{
public:ThreadWork() = default;~ThreadWork() = default;
public:
public:std::queue> qTask_;std::queue qW_;//std::queue> qpW_;
};void TestTask()
{//分三种情况//普通函数TaskWrapper tw1{ WorkTask };tw1();//仿函数TaskWrapper tw2{ WorkTaskFunc{} };tw2();//Lambda表达式TaskWrapper tw3{ tasklambda };tw3();ThreadWork tWork;tWork.qW_.emplace(std::move(tw1));tWork.qW_.emplace(std::move(tw2));tWork.qW_.emplace(std::move(tw3));auto t1 = std::move(tWork.qW_.front());tWork.qW_.pop();auto t2 = std::move(tWork.qW_.front());tWork.qW_.pop();auto t3 = std::move(tWork.qW_.front());tWork.qW_.pop();t1();t2();t3();}
int main()
{TestTask();system("pause");return 0;
}

这个至简的框架Demo,只是一个基本的描述,到一个真正的工程模块,还有遥远的距离,但是从思想上掌握了这种开发的方式,就能够更好的指导着代码的设计和开发,这才是学习别人借鉴别人的经验的意义。学以致用,这才是最重要的。
在上面如果队列使用智能指针,或者在打包类中等使用std::shared_ptr,就可以不进行移动复制等函数的控制,但那样做的话,前者应用起来不是特别友好,不符合开发者的习惯;后者在多线程中就可能产生对象的控制的同步复杂性。

三、总结

从很早就从网上书上看到很多牛人写这个任务队列,自己也曾经反复尝试写过类似的多线程队列操作,都非常不满意。即使到现在觉得这个东西仍然有很大的完善和修改空间,有机会还是要在实际场景中对此类型的需求进行一次整体的重构。希望还能找到这个机会。

相关内容

热门资讯

微信安卓系统转苹果系统,轻松实... 你有没有想过,从微信安卓系统转到苹果系统,这中间的转换过程,就像是一场说走就走的旅行,充满了未知和惊...
如何刷安卓8.0系统,安卓8.... 你有没有想过,让你的安卓手机升级到最新的8.0系统,让它焕发出全新的活力呢?别急,今天我就来给你详细...
安卓系统里查看路由,安卓系统下... 你是不是也和我一样,对家里的无线网络充满了好奇?想知道安卓手机里怎么查看路由器信息?那就跟着我一起探...
手机出现安卓系统信号,手机信号... 你有没有发现,最近你的安卓手机信号好像变得特别不稳定呢?是不是觉得有时候信号满格,却还是接不到电话,...
创维安卓系统怎么安装,享受智能... 你家的创维电视是不是最近有点儿不给力,想要给它来个升级,让它焕发新生呢?那就得给它装个安卓系统啦!别...
中兴刷原生安卓系统,原生安卓系... 亲爱的读者们,你是否厌倦了那些千篇一律的安卓系统,想要给你的手机来点新鲜感?今天,就让我带你一起探索...
云系统与安卓系统软件,构建智能... 你有没有想过,你的手机里那些神奇的软件,其实都是靠云系统和安卓系统软件的默契配合才变得如此强大呢?想...
如何禁止安卓系统联网,全方位操... 你有没有想过,你的安卓手机其实是个小宇宙,里面藏着无数的秘密和信息?但是,你知道吗?有时候,这些信息...
a安卓系统不兼容,揭秘a设备的... 最近是不是发现你的安卓手机有些不对劲?比如,某个APP突然罢工了,再比如,你下载了一个新游戏,结果发...
安卓系统刷固件教程,解锁设备潜... 你有没有想过,你的安卓手机其实就像一个隐藏着无限可能的宝藏呢?没错,就是那个你每天不离手的宝贝。今天...
电脑系统安卓界面,功能与美学的... 你有没有发现,现在手机和电脑的界面越来越像了呢?没错,就是那个我们每天都要打交道的好伙伴——安卓界面...
吃鸡王座安卓系统,登顶吃鸡巅峰 你有没有想过,在手机游戏中,谁才是真正的“吃鸡王座”呢?今天,就让我带你一探究竟,看看安卓系统上的那...
安卓点名系统下载,安卓点名系统... 你有没有想过,在繁忙的学习生活中,有没有一种神奇的工具,能让你轻松管理课堂纪律,还能让点名变得如此有...
手机安装通用安卓系统,引领智能... 你有没有想过,为什么你的手机可以安装那么多好玩的应用?秘密就在于它搭载了通用安卓系统!想象一个系统就...
安卓系统仿真器,功能与操作指南 你有没有想过,在电脑上也能玩安卓游戏?没错,这就是安卓系统仿真器的神奇之处!想象你坐在电脑前,手握鼠...
安卓系统可以刷街机,畅享虚拟游... 你知道吗?现在用安卓系统刷街机,简直就像变魔术一样神奇!没错,就是那种让你仿佛穿越回童年,手握游戏杆...
安卓系统画画软件画笔,绘制无限... 你有没有发现,手机里的画画软件越来越丰富啦?尤其是安卓系统上的那些,简直让人眼花缭乱。今天,就让我带...
安卓系统垃圾和缓存,提升使用体... 手机里的安卓系统是不是越来越慢了?是不是觉得打开一个应用都要等半天?别急,今天就来跟你聊聊安卓系统里...
安卓系统图片转入苹果,轻松实现... 你是不是也有过这样的烦恼?手机里存了好多珍贵的照片,突然想换手机,却发现安卓系统的照片怎么也弄不到苹...
华为matebooke装安卓系... 你有没有想过,你的华为MateBook也能装上安卓系统呢?没错,就是那个我们平时手机上用的安卓系统!...