(原创)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++中的类型擦除的更多相关文章

  1. Java中泛型 类型擦除

    转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...

  2. Java中的类型擦除与桥方法

    类型擦除 Java在语法中虽然存在泛型的概念,但是在虚拟机中却没有泛型的概念,虚拟机中所有的类型都是普通类.无论何时定义一个泛型类型,编译后类型会被都被自动转换成一个相应的原始类型. 比如这个类 pu ...

  3. JAVA泛型中的类型擦除及为什么不支持泛型数组

    一,数组的协变性(covariant array type)及集合的非协变性 设有Circle类和Square类继承自Shape类. 关于数组的协变性,看代码: public static doubl ...

  4. (原创)c++中的类型擦除

    c++11 boost技术交流群:296561497,欢迎大家来交流技术. 关于类型擦除,可能很多人都不清楚,不知道类型擦除是干啥的,为什么需要类型擦除.有必要做个说明,类型擦除就是将原有类型消除或者 ...

  5. Java泛型中的类型擦除机制简单理解

    Java的泛型是JDK1.5时引入的.下面只是简单的介绍,不做深入的分析. Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

  6. swift中的"类型擦除"

    代理模式.或者协议模式 因为swift泛型还不支持逆变和协变也就不会有真的类型擦除,而这里说的"类型擦除"是指:利用一个具体实现的通用泛型类(参看系统库的AnySequence), ...

  7. java 泛型的类型擦除与桥方法

    泛型类 --代码参考:java核心技术 卷1 第十版 public class Pair<T> { private T first; private T second; //构造器 pub ...

  8. 从 Swift 中的序列到类型擦除

    如果有这样的一个需求,我希望能像数组一样,用 for 循环遍历一个类或结构体中的所有属性.就像下面这样: let persion = Persion() for i in persion { prin ...

  9. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

随机推荐

  1. 【百度地图API】发布静态图API啦!只需一个网址,即可展示定制百度地图!

    原文:[百度地图API]发布静态图API啦!只需一个网址,即可展示定制百度地图! 摘要: 百度地图静态图API!您无须执行任何“特殊”操作便可在网页上显示此图片. 不需要 JavaScript.我们只 ...

  2. [ACM] poj 1064 Cable master (二进制搜索)

    Cable master Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 21071   Accepted: 4542 Des ...

  3. 使用JFinal框架中Validator

    Validator是JFinal框架中的校验组件,在Validator类中提供了我们经常使用的校验方法,而Validator本身实现了Interceptor接口,所以Validator也相当于一个拦截 ...

  4. 引擎介绍 - REngine

    引擎介绍 - REngine 规则引擎 规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规 ...

  5. App根据第,HTML5流行?

    App根据第.HTML5流行? 引用新闻 日前,有消息称国家网信办近日将出台APP应用程序发展管理办法.中央网信办主任鲁炜在推进网络空间法治化座谈会上透露.我国将加强互联网立法,依靠严密的法律网来打造 ...

  6. NGUI ScrollView动态加入和删除对象。

    动态加入,基本思想是: 1.先把要加入的元素在编辑器中编辑好,制作成一个prefab. 2.在代码中,动态的生成一个新的对象增加到Grid对象的子对象中.这里利用到了Resources对象,这个对象的 ...

  7. Hadoop -YARN 应用程序设计概述

    一概述        应用程序是用户编写的处理数据的统称,它从YARN中申请资源完毕自己的计算任务.YARN自身相应用程序类型没有不论什么限制,它能够是处理短类型任务的MapReduce作业,也能够是 ...

  8. table居中方法之一:设置width,然后为style设置margin:auto

    比如: <table width="800px" style="margin:auto;">

  9. nginx配置文件中的location详解

    location 语法:location [=|~|~*|^~] /uri/ { … } 默认:否 上下文:server 这个指令随URL不同而接受不同的结构.你可以配置使用常规字符串和正则表达式.如 ...

  10. 【转】 Android项目的mvc模式

    MVC (Model-View-Controller):M是指逻辑模型,V是指视图模型,C则是控制器.一个逻辑模型M可以对于多种视图模型V,比如一批统计数据你可以分别用柱状图.饼图V来表示.一种视图模 ...