c++中的类型擦除
(原创)c++中的类型擦除
c++11 boost技术交流群:296561497,欢迎大家来交流技术。
关于类型擦除,可能很多人都不清楚,不知道类型擦除是干啥的,为什么需要类型擦除。有必要做个说明,类型擦除就是将原有类型消除或者隐藏。为什么要擦除类型?因为很多时候我不关心具体类型是什么或者根本就不需要这个类型,通过类型擦除我们可以获取很多好处,比如使得我们的程序有更好的扩展性、还能消除耦合以及消除一些重复行为,使程序更加简洁高效。归纳一下c++中类型擦除方式主要有如下五种:
第一种:通过多态来擦除类型
第二种:通过模板来擦除类型
第三种:通过某种容器来擦除类型
第四种:通过某种通用类型来擦除类型
第五种:通过闭包来擦除类型
第一种类型隐藏的方式最简单也是我们经常用的,通过将派生类型隐式转换成基类型,再通过基类去多态的调用行为,在这种情况下,我不用关心派生类的具体类型,我只需要以一种统一的方式去做不同的事情,所以就把派生类型转成基类型隐藏起来,这样不仅仅可以多态调用还使我们的程序具有良好的可扩展性。然而这种方式的类型擦除仅仅是部分的类型擦除,因为基类型仍然存在,而且这种类型擦除的方式还必须是继承方式的才可以,而且继承使得两个对象强烈的耦合在一起了,正是因为这些缺点,通过多态来擦除类型的方式有较多局限性效果也不好。这时我们通过第二种方式擦除类型,以解决第一种方式的一些缺点。通过模板来擦除类型,本质上是把不同类型的共同行为进行了抽象,这时不同类型彼此之间不需要通过继承这种强耦合的方式去获得共同的行为了,仅仅是通过模板就能获取共同行为,降低了不同类型之间的耦合,是一种很好的类型擦除方式。然而,第二种方式虽然降低了对象间的耦合,但是还有一个问题没解决,就是基本类型始终需要指定,并没有消除基本类型,例如,我不可能把一个T本身作为容器元素,必须在容器初始化时就要知名这个T是具体某个类型。这时多么希望有一种通用的类型啊,可以让我的容器容纳所有的类型,就像c#和java中的object类型一样,是所有类型的基类。c++中没有这种object类型怎么办?也许有人想到了,可以用boost.variant类型,是的,boost.variant可以把各种不同的类型包起来,从而让我们获得了一种统一的类型,而且不同类型的对象间没有耦合关系,它仅仅是一个类型的容器。让我们看看怎么用boost.variant来擦除类型。
struct blob
{
const char *pBuf;
int size;
};
//定义通用的类型,这个类型可能容纳多种类型
typedef boost::variant<double, int, uint32_t, sqlite3_int64, char*, blob, NullType>Value;
vector<Value> vt; //通用类型的容器,这个容器现在就可以容纳上面的那些类型的对象了
vt.push_back(1);
vt.push_back("test");
vt.push_back(1.22);
vt.push_back({"test", 4});
上面的代码就擦除了不同类型,使得不同的类型都可以放到一个容器中了,如果要取出来就很简单,通过get<T>(Value)就可以获取对应类型的值了。这种方式是通过某种容器把类型包起来了,从而达到类型擦除的目的。它的缺点是这个通用的类型必须事先定义好,它只能容纳声明的那些类型,增加一种新类型就不行了。通过第四种方式可以消除这个缺点,通过某种通用类型来擦除类型。类似于c#和java中的object类型。这种通用类型是通过boost.any实现的,它不需要预先定义类型,不同类型都可以转成any。让我们看看怎么用any来擦除类型的。

unordered_map<string, boost::any> m_creatorMap;
m_creatorMap.insert(make_pair(strKey, new T)); //T may be any type
boost::any obj = m_creatorMap[strKey];
T t = boost::any_cast<T>(obj);

需要注意的是,第四和第五种方式虽然解决了第三种方式不能彻底消除基本类型的缺点,但是还存一个缺点,就是取值的时候仍然依赖于具体类型,无论我是通过get<T>还是any_case<T>,我都要T的具体类型,这在某种情况下仍然有局限性。例如,有这样一种场景:
我有A、B、C、D四种结构体,每个结构体中有某种类型的指针,名称且称为info,我现在提供了返回这些结构体的四个接口供外接使用,有可能是c#或者dephi调用这些接口,由于结构体中的info指针是我分配的内存,所以我必须提供释放这些指针的接口。代码如下:

struct A
{
int* info;
int id;
}; struct B
{
double* info;
int id;
}; struct C
{
char* info;
int id;
}; struct D
{
float* info;
int id;
}; //对外提供的删除接口
void DeleteA(A& t)
{
delete t.info;
} void DeleteB(B& t)
{
delete t.info;
} void DeleteC(C& t)
{
delete t.info;
} void DeleteD(D& t)
{
delete t.info;
}

大家可以看到,增加的四个删除函数内部都是重复代码,本来通过模板函数一行搞定,但是没办法,c#可没有c++的模板,还得老老实实的提供这些重复行为的接口,而且这种方式还有个坏处就是每增加一种类型就得增加一个重复的删除接口,怎么办?能统一成一个删除接口吗?可以,一个可行的办法就是将分配的内存通过一个ID关联并保存起来,让外接传一个ID,告诉我要删那块内存,新的统一删除函数可能是这样:

//内部将分配的内存存到map中,让外面传ID,内部通过ID去删除对应的内存块
map<int, T> mapT; template<typename R, typename T>
R GetT()
{
R result{1,new T()};
mapT.insert(std::pair<int, T>(1, R));
return result;
} //通过ID去关联我分配的内存块,外面传ID,内部通过ID去删除关联的内存块
void DeleteT(const int& id)
{
R t = mapT[id]->second();
delete t.info;
}

很遗憾,上面的代码编译不过,因为,map<int, T> mapT只能保存一种类型的对象,无法把分配的不同类型的对象保存起来,我们可以通过方式三和方式四,用variant或者any去擦除类型,解决T不能代表多种类型的问题,第一个问题解决。但是还有第二个问题,DeleteT时,从map中返回的variant或者any,无法取出来,因为接口函数中没有类型信息,而取值方法get<T>和any_cast<T>都需要一个具体类型。似乎进入了死胡同,无法只提供一个删除接口了。但是办法总还是有的。
方式五隆重登场了,看似无解的问题,通过方式五就能解决了。通过闭包来擦除类型很好很强大。在介绍方式五之前,我要先介绍一下闭包,闭包也可以称为匿名函数或者lamda表达式,c++11中的lamda表达式就是c++中的闭包,c++11引入lamda,实际上引入了函数式编程的概念,函数式编程有很多优点,使代码更简洁,而且声明式的编码方式更贴近人的思维方式。函数式编程在更高的层次上对不同类型的公共行为进行了抽象,从而使我们不必去关心具体类型。关于函数式编程的优点就不多说了。下面看看如何使用方式五去解决上面的问题。

std::map < int, std::function <void()>> m_freeMap; //保存返回出去的内存块 template<typename R, typename T>
R GetResult()
{
R result = GetTable<R, T>(); m_freeMap.insert(std::make_pair(result.sequenceId, [this, result]
{
FreeResult(result);
}));
} bool FreeResultById(int& memId)
{
auto it = m_freeMap.find(memId);
if (it == m_freeMap.end())
return false; it->second(); //delete by lamda
m_freeMap.erase(memId); return true;
}

总结:通过闭包去擦除类型,可以解决前面四种擦除方式遇到的问题,优雅而简单!
c++中的类型擦除的更多相关文章
- Java中泛型 类型擦除
转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...
- Java中的类型擦除与桥方法
类型擦除 Java在语法中虽然存在泛型的概念,但是在虚拟机中却没有泛型的概念,虚拟机中所有的类型都是普通类.无论何时定义一个泛型类型,编译后类型会被都被自动转换成一个相应的原始类型. 比如这个类 pu ...
- JAVA泛型中的类型擦除及为什么不支持泛型数组
一,数组的协变性(covariant array type)及集合的非协变性 设有Circle类和Square类继承自Shape类. 关于数组的协变性,看代码: public static doubl ...
- (原创)c++中的类型擦除
c++11 boost技术交流群:296561497,欢迎大家来交流技术. 关于类型擦除,可能很多人都不清楚,不知道类型擦除是干啥的,为什么需要类型擦除.有必要做个说明,类型擦除就是将原有类型消除或者 ...
- Java泛型中的类型擦除机制简单理解
Java的泛型是JDK1.5时引入的.下面只是简单的介绍,不做深入的分析. Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- swift中的"类型擦除"
代理模式.或者协议模式 因为swift泛型还不支持逆变和协变也就不会有真的类型擦除,而这里说的"类型擦除"是指:利用一个具体实现的通用泛型类(参看系统库的AnySequence), ...
- java 泛型的类型擦除与桥方法
泛型类 --代码参考:java核心技术 卷1 第十版 public class Pair<T> { private T first; private T second; //构造器 pub ...
- 从 Swift 中的序列到类型擦除
如果有这样的一个需求,我希望能像数组一样,用 for 循环遍历一个类或结构体中的所有属性.就像下面这样: let persion = Persion() for i in persion { prin ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
随机推荐
- Google Guice结合模式
于Guice于,喷油器装配工作是一个对象图,当请求类型实例,喷油器根据推断对象如何映射到创建的实例.解决依赖.要确定如何解决的依赖就需要配置喷油器结合的方式. 要创建绑定(Binding)对象,能够继 ...
- 2299 Ultra-QuickSort(归并)
合并排序第一次.连环画看着合并看着别人的博客的想法. http://poj.org/problem? id=2299 #include <stdio.h> #include <std ...
- sgu139Help Needed!推断15数码是否有解,以及推断N数码是否有解的推论
是这种,要你推断一个15数码是否有解. 我不会,找了这样一个方法. 将16个数按出现顺序存放在一维数组里面, 然后累加每一个数的逆序对数目, 还要加上0到终态的曼哈顿距离,得到一个数x. 因为最后的状 ...
- 批量执行SQL文件
原文:批量执行SQL文件 摘要:很多时候我们在做系统升级时需要将大量的.sql文件挨个执行,十分不方便.而且考虑到执行顺序和客服的操作方便性,能不能找到一种简单的方法来批量执行这些sql文件呢? 主要 ...
- sql优化的50中方法
查询速度慢的原因很多,常见如下几种: 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化 ...
- Web API-路由(一)
这一篇文章描述了ASP.NET API如何将HTTP请求路由到控制器. 如果你熟悉ASP.NET MVC,Web API路由和MVC路由非常相似,主要的不同地方是Web API使用HTTP方法,而不是 ...
- 小结php中几种网页跳转
1.使用网页中<a href=.....></a>实现跳转: 2.<form action="php_request2.php" method=&qu ...
- js 正则之检测素数
原文:js 正则之检测素数 相信很多人应该看过这篇文章,我第一次看到的时候是11年的样子,那时候学vbs的时候看过这个问题.原文<检查素数的正则表达式>,在文章里已经解释了他是怎么判断的, ...
- [翻译]MEAN.IO与MEAN.JS的前世今生
写在前面: 因为项目要求要学习mean,所以要从搭建环境开始,网上的教程不怎么多,但是感觉方法倒是不少,也没多想,就开始安装,运行起来发现怎么和安装教程中的目录结构不同? 想了好久,也翻看目录半天,总 ...
- Java集合之ArrayList源码分析
1.简介 List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList.Java中分别提供了这两种结构的 ...