一、类型擦除

很多人一直都认为,类型擦除是一些高级语言(如Java)才具有的,其实在c++中也可以实现类型擦除。那么什么是类型擦除呢?我们都知道,C/c++是一门强类型语言,也就是说,编译器必须知道数据是属于什么类型的。说的直白一点,就是int类型还是其它什么类型,当然这些类型里也包括对象类型。而类型擦除,就是要把这些数据的类型抹去,或者说擦除掉,当然也可以理解为隐藏掉数据的类型。这不和刚刚说的强类型语言相反么,这样做有什么用处呢?
做为强类型语言,所有的数据类型必须强调可知就会有一个显示的问题,无法用一个通过的定义来描述这些数据类型非特定的行为。而往往是这些行为决定了设计上的解耦和分离。而这种行为的不同就是程序设计可扩展性和简洁高效的前提和基础,它可以显著的降低程序中的侵入式设计。而在前面也反复提到过,c++从设计上本身是无法提供在运行时动态获取数据对象的类型的功能的。这也让类型擦除更具重要性。
这里需要提到一个定义:鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由“当前方法和属性的集合”决定。这玩意儿有啥用呢,它可以不通过继承实现多态。

二、类型擦除的方式

那么数据类型有几种方式呢,常见的有以下几种:
1、多态
多态一般都很了解,可以通过父类型指针来管理子类型的指针,实现了对子对象类型的隐藏。但这种方式的缺点也非常明显,需要写继承的类而且数据类型的隐藏并不全面,至少父类型始终要暴露出来。
2、模板
模板在前面也提到过多次,通过模板,可以实现对多种类型的行为操作进行抽象。比如一个模板的加法函数(比如多个类都调用此加法函数),可以实现对不同的数据类型的操作,如果对象也重载了加法,同样也可以使用。不过,在基本类型中,这种模板函数抽象的行为就无能为力了。
3、容器
容器之所以可以擦除,其实更类似于模板,这个不用多说。另外还有一种组合容器,其实有些类似共用体。比如std::variant 。但此种方式其实也明确了数据的类型,只是多了几种罢了,让编译器在真正使用时进行选择。所以这种类型擦除更接近一种类型判断。
4、通用类型
这个就得提到前面分析的std::any,通过它可以进行数据类型的动态转换。但是其才应用时,仍然需要指定具体的类型后才能使用。
5、闭包
所谓闭包,其实就是Lambda表达式,通过它和std::function的配合,实现类型的擦除。

从上面的分析可以看到,其实具体实现上来看,如果想实现的比较理想(如any等的实现基本就是有限定范围的),基本上都需要两个基本条件,一个是带约束的模板构造函数,另外一个就是函数指针。再回头和SBO系列相对比一下就可以明白为啥std::function的实现机制中的基本方式了。同时,也可以搞明白,其实类型擦除就是一种使用函数指针来实现多态的机制。

三、应用场景

类型擦除的应用场景非常多,特别是在设计中,如何实现一个非继承方式来搞定扩展而勿需考虑数据类型时,都可以使用这种方式。当然,如果看过MFC实现的源码,也可以使用类似其侵入式的设计来实现,但那个太复杂了,而且可扩展性也有限。业界现在基本已经不在采用那种方式来设计程序。
另外上一篇中SBO系列中,其实也是采用了这种方式来实现。只要能够明白函数指针的机制,这种设计就可以不拘泥于某种场景,自由的发挥。而所谓类型擦除不过是其中一个应用的重要实现而已。

四、例程

这里的例程使用一个同学的例程,大家参考分析一下就明白了。


class MyFunction
{
private:class FunctorWrapper{public:virtual ~FunctorWrapper() = default;virtual FunctorWrapper* clone() const = 0;virtual void call() const = 0;};template<typename T>class ConcreteWrapper : public FunctorWrapper{public:ConcreteWrapper(const T& functor): functor(functor) { }virtual ~ConcreteWrapper() override = default;virtual ConcreteWrapper* clone() const{return new ConcreteWrapper(*this);}virtual void call() const override{functor();}private:T functor;};
public:MyFunction() = default;template<typename T>MyFunction(T&& functor): ptr(new ConcreteWrapper<T>(functor)) { }MyFunction(const MyFunction& other): ptr(other.ptr->clone()) { }MyFunction& operator=(const MyFunction& other){if (this != &other){delete ptr;ptr = other.ptr->clone();}return *this;}MyFunction(MyFunction&& other) noexcept: ptr(std::exchange(other.ptr, nullptr)) { }MyFunction& operator=(MyFunction&& other) noexcept{if (this != &other){delete ptr;ptr = std::exchange(other.ptr, nullptr);}return *this;}~MyFunction(){delete ptr;}void operator()() const{if (ptr)ptr->call();}FunctorWrapper* ptr = nullptr;
};

再看一下不使用继承的:

class MyFunction
{
private:static constexpr std::size_t size = 16;static_assert(size >= sizeof(void*), "");struct Data{Data() = default;char dont_use[size];} data;template<typename T>static void functorConstruct(Data& dst, T&& src){using U = typename std::decay<T>::type;if (sizeof(U) <= size)new ((U*)&dst) U(std::forward<U>(src));else*(U**)&dst = new U(std::forward<U>(src));}template<typename T>static void functorDestructor(Data& data){using U = typename std::decay<T>::type;if (sizeof(U) <= size)((U*)&data)->~U();elsedelete *(U**)&data;}template<typename T>static void functorCopyCtor(Data& dst, const Data& src){using U = typename std::decay<T>::type;if (sizeof(U) <= size)new ((U*)&dst) U(*(const U*)&src);else*(U**)&dst = new U(**(const U**)&src);}template<typename T>static void functorMoveCtor(Data& dst, Data& src){using U = typename std::decay<T>::type;if (sizeof(U) <= size)new ((U*)&dst) U(*(const U*)&src);else*(U**)&dst = std::exchange(*(U**)&src, nullptr);}template<typename T>static void functorInvoke(const Data& data){using U = typename std::decay<T>::type;if (sizeof(U) <= size)(*(U*)&data)();else(**(U**)&data)();}template<typename T>static void (*const vtables[4])();void (*const* vtable)() = nullptr;
public:MyFunction() = default;template<typename T>MyFunction(T&& obj): vtable(vtables<T>){functorConstruct(data, std::forward<T>(obj));}MyFunction(const MyFunction& other): vtable(other.vtable){if (vtable)((void (*)(Data&, const Data&))vtable[1])(this->data, other.data);}MyFunction& operator=(const MyFunction& other){this->~MyFunction();vtable = other.vtable;new (this) MyFunction(other);return *this;}MyFunction(MyFunction&& other) noexcept: vtable(std::exchange(other.vtable, nullptr)){if (vtable)((void (*)(Data&, Data&))vtable[2])(this->data, other.data);}MyFunction& operator=(MyFunction&& other) noexcept{this->~MyFunction();new (this) MyFunction(std::move(other));return *this;}~MyFunction(){if (vtable)((void (*)(Data&))vtable[0])(data);}void operator()() const{if (vtable)((void (*)(const Data&))vtable[3])(this->data);}
};template<typename T>
void (*const MyFunction::vtables[4])() =
{(void (*)())MyFunction::functorDestructor<T>,(void (*)())MyFunction::functorCopyCtor<T>,(void (*)())MyFunction::functorMoveCtor<T>,(void (*)())MyFunction::functorInvoke<T>,
};

有一个可能约束的模板构造函数和函数指针。上面的封装需要相关的模板类型实现了拷贝和仿函数的功能,否则不能使用。更多的可以参考一下原文:
https://www.cnblogs.com/jerry-fuyi/p/12664787.html#sbo
正所谓长江后浪推前浪,这位同学的博客质量相当高,大家可以看一看。
也可以参考:
https://quuxplusone.github.io/blog/2019/03/18/what-is-type-erasure/
https://zhuanlan.zhihu.com/p/351291649
https://zhuanlan.zhihu.com/p/351464404
https://zhuanlan.zhihu.com/p/374562057
一般来说,在一元类型操作中(换句话说就是函数中一般只有一个模板参数)表现很良好,而有多个选项的话,则不太友好。

五、总结

现在大家都很惶惑,不知道未来的方向。可能抬眼望去,都是不可预知的迷茫。越是在这个时候儿,越是要沉住气,要不断的锤炼自己的技术,趁着这个机会把基础打得更牢更扎实;同时,要不断的寻找方向,不要因为找不到方向就焦虑,因为可能下一次就会发现正确的方向。没有方向要大胆去找,有了方向要大胆去试。
特别是年青人,现在属于你们的世界。努力挥洒汗水,光明就在拐角。

跟我学c++中级篇——类型擦除相关推荐

  1. 跟我学c++中级篇——concepts的几个应用

    一.concepts的入门应用 concepts的应用是一个非常必要的问题.它对于模板在实际编程中的友好性有着至关重要的作用.先从最简单的一个示例说起: struct PlusSum{int d_ = ...

  2. 跟我学c++中级篇——STL字符串之std::string_view

    一.标准库字符串处理 C和C++的一个很不一样的区别就是对字符串的处理,在c++的标准库里提供了一个std::string的字符串操作类.这使得c++对字符串的操作从某种程度上摆脱了原始指针的操作.从 ...

  3. 『中级篇』k8s的NodePort类型Service以及Label的简单实用(68)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『中级篇』k8s的NodePort类型Service以及Label的简单实用(68) 上次主要说了service的一种类型,c ...

  4. Java基础篇:泛型与类型擦除

    一.什么是泛型: 泛型的本质是 参数化类型,也就是说 将所操作的数据类型 指定为一个参数,在不创建新类的情况下,通过参数来指定所要操作的具体类型(类似于方法中的变量参数,此时类型也定义成参数形式),也 ...

  5. 『中级篇』docker导学(一)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『中级篇』docker导学(一) 这两年容器技术及其相关工具,平台异常火爆.在各大技术论坛或云计算峰会议题中,都会占很大比重, ...

  6. Unity 回合制战斗系统(中级篇)

    项目文件找出来了,老版本的脚本有报错,我在新版2019.4.21f1c1下解决了报错,战斗场景可以正常跑的. 需要的同学点下面地址下载(关注就行啦不用积分),祝大家都早日学成 项目包下载 ------ ...

  7. 视频教程-C# For Unity系列之中级篇-Unity3D

    C# For Unity系列之中级篇 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过程.软件架构设计理 ...

  8. 零基础学习openstack【完整中级篇】及openstack资源汇总

    1.你是如何学习openstack的? 2.你对openstack的组件了解多少? 3.你认为openstack该如何学习? 一直想写关于openstack的方面的内容,今天终于整理完成.算是完成一桩 ...

  9. 『中级篇』docker容器安装wordpress(37)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『中级篇』docker容器安装wordpress(37) 第一节的时候我就部署过wordpress,可能很多老铁一头雾水不知道 ...

最新文章

  1. 【怎样写代码】向现有类型“添加”方法 -- 扩展方法(一):扩展方法概述
  2. 回文java_回文 Java
  3. 全球及中国水产养殖和畜牧保险行业风险研究与“十四五”投资建议报告2021年版
  4. 合法的python变量名import_python 环境变量和import模块导入方法(详解)
  5. 测度论相关概念(吐)
  6. 左神算法:判断二叉树是否为平衡二叉树(树形dp套路,Java版)
  7. Asp.net2.0:如何使用ObjectDataSource
  8. [学习笔记] 二次剩余
  9. php扩展 waf,基于PHP扩展的WAF实现
  10. HTTP状态:202、301、304、404、503
  11. 数据库工作笔记009---linux 导入导出postgresql数据库
  12. 【王牌选手分享】一发问鼎!鹅厂大神上分思路,助你玩转初赛!
  13. 强化学习的数学基础2---PPO算法
  14. visio 2007 简体中文版下载
  15. 网络工程师(软考)心得
  16. 解决问题:Unable to connect to Redis
  17. 快速去掉迅雷上的弹窗广告
  18. 19 Three.js实现雾化效果
  19. 计算机ram和rom的特点的是,什么是ROM和RAM?它们各有什么特点?
  20. 数学建模方法-灰色预测

热门文章

  1. Ubuntu使用OpenMVG和OpenMVS进行三维重建
  2. android studio 主题样式,AndroidStudio主题样式
  3. 联咏电子科技(西安)笔试、面试经历
  4. 获取手机的指南针和行进方向
  5. 什么是 IAT(一)
  6. 数据驱动的智能运维平台
  7. win10打开PLC通信出现未找到指定的访问点
  8. ssd硬盘linux要分区吗,2020年,你还会对硬盘/SSD进行分区吗?
  9. 强大的终端模拟器:Termux
  10. 强大的Android汉字转拼音开源库TinyPinyin