(原创)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. SOA、REST 和六边形架构

    SOA.REST 和六边形架构 上一篇:<IDDD 实现领域驱动设计-架构之经典分层> 阅读目录: SOA-面向服务架构 REST 与 RESTful 资源(Resources) 状态(S ...

  2. Codeforces #180 div2 C Parity Game

    // Codeforces #180 div2 C Parity Game // // 这个问题的意思被摄物体没有解释 // // 这个主题是如此的狠一点(对我来说,),不多说了这 // // 解决问 ...

  3. 设计模式组合模式(Composite)精华

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,他们帮助如何创建一个系统独立.这是一个这些对象和陈述的组合. 创建使用继承类的类架构更改实例.的对象类型模型的建 ...

  4. POJ 3255 Roadblocks (次级短路问题)

    解决方案有许多美丽的地方.让我们跳回到到达终点跳回(例如有两点)....无论如何,这不是最短路,但它并不重要.算法能给出正确的结果 思考:而最短的路到同一点例程.spfa先正达恳求一次,求的最短路径的 ...

  5. mongodb group包(最具体的、最受欢迎、最容易理解的解释)

    和数据库一样group经常常使用于统计.MongoDB的group还有非常多限制,如:返回结果集不能超过16M, group操作不会处理超过10000个唯一键.好像还不能利用索引[不非常确定]. Gr ...

  6. MVC5 + EF6 + Bootstrap3 (8) HtmlHelper

    MVC5 + EF6 + Bootstrap3 (8) HtmlHelper用法大全(上) 上一节:MVC5 + EF6 + Bootstrap3 (7) Bootstrap的栅格系统 源码下载:点我 ...

  7. 苹果iOS苹果公司的手机用户都有权索赔

    大家知道.手机中的操作系统(基础软件)存储在手机固(firm,ware)之中,一般而言,手机用户自己是不能修改的. 苹果iOS手机的系统后门(服务程序)也存储在手机固件之中.手机用户自己是无法删除的. ...

  8. 快速构建Windows 8风格应用37-常见发布注意事项

    原文:快速构建Windows 8风格应用37-常见发布注意事项 引言 通常我们发布Windows Store应用失败后,会返回一些错误需要我们去修改.我之前在给学生做培训的时候发现大部分同学应用被打回 ...

  9. leetcode第37题--Count and Say

    题目:(据说是facebook的面试题哦) The count-and-say sequence is the sequence of integers beginning as follows:1, ...

  10. office编程必不可少

    原文:office编程必不可少 [转] 1. 微软官方实例: 段落.表格.图表 HOW TO:利用 Visual C# .NET 使 Word 自动新建文档 2. 学习资源 (1)Word in th ...