C++是强类型语言,所有强类型语言对型别的要求都是苛刻的,型别一有不合编译器就会抱怨说不能将某某型别转换为某某型别,当然如果在型别之间提供了转换操作符或是标准所允许的一定程度的隐式转换(如经过非explicit构造函数创建临时变量的隐式转换或是在int,long这些基本型别间的)又另当别论。总的说来,为了保持型别安全,C++有严厉的要求。然而有时候程序员可能有这样的需要:
- int i;
- iong j;
- X x; //假设X为用户定义的类
- any anyVal=i;
- ... //use anyVal as a int value
- anyVal=j;
- ... //use anyVal as a long value
- anyVal=x;
- ... //use anyVal as a long value
考虑这样的一个“泛型指针类”该如何设计是很有趣的事情。
1.它本身不能是模板类,因为如果它是模板,你必须为它的具现化提供模板参数。而事实上你并不想这样做。你想让同一个对象接受任意型别的数据。在上面的代码中这个对象是anyVal。然而,如果你必须为它提供模板参数,那么上面的代码看起来就会像这样:
- any<int> anyIntVal=i;
- any<long> anyLongVal=j;
- ...
这显然已经丧失了anyVal的优势----以单个对象接受所有型别的数据。与其这样还不如直接写:
- int anyIntVal=i;
- int anyLongVal=j;
所以,any不能是模板类。
2.它必须提供某些有关它所保存的对象型别的信息。
3. 它必须提供某种方法将它保存的数值“取出来”。
事实上,Boost库已经提供了这样的类boost::any,下面我就为你讲述它的原理及构造。
首先,any类里面一定要提供一个模板构造函数和模板operator=操作符。因为你必须允许用户写出:
- any any_value(val); //val 的型别为任意的
- any_value=val1; //val1 型别也是任意的
这样的代码。
其次,数据的存放之所是个问题,显然你不能将它保存在any类中,那会导致any类成为模板类,后者是明确不被允许的。数据应该动态存放,即动态分配一个数据的容器来存放数据,而any类中则保存指向这个容器的指针,明确地说,是指向这个容器的基类的指针,这是因为容器本身必须为模板,而any类中的指针成员又必须不是泛型的(因为any不能是泛型的,所以any中所有数据成员都不能是泛型的),所以,结论是:为容器准备一个非泛型的基类,而让指针指向该基类。
下面就看一看boost库是如何具体实现这两点的。
- //摘自”boost/any.hpp”
- class any
- {
- public:
- class placeholder //泛型数据容器holder的非泛型基类
- {
- public: // structors
- virtual ~placeholder() //虚析构函数,为保证派生类对象能用基类指针析构
- {}
- public: // queries
- virtual const std::type_info & type() const = 0; //提供关于型别的信息
- virtual placeholder * clone() const = 0; //复制容器
- };
- template<typename ValueType>
- class holder : public placeholder //
- {
- public: // structors
- holder(const ValueType & value) //
- : held(value)
- {}
- public: // queries
- virtual const std::type_info & type() const
- {
- return typeid(ValueType); //typeid返回std::typeinfo对象引用,后者包含任意
- //对象的型别信息如name,还提供operator==操作符
- //你可以用typeid(oneObj)==typeid(anotherObj)来比
- //两个对象之型别是否一致
- }
- virtual placeholder * clone() const
- {
- return new holder(held); //改写虚函数,返回自身的复制体
- }
- public: // representation
- ValueType held; //数据保存的地方
- };//类定义结束
- placeholder * content; //指向泛型数据容器holder的基类placeholder的指针
- template<typename ValueType>
- any(const ValueType & value)
- : content(new holder<ValueType>(value)) //模板构造函数,动态分配数据容器并调用其构
- //造函数
- {}
- ...
- template<typename ValueType>
- any & operator=(const ValueType & rhs) //与模板构造函数一样,但使用了swap惯用手法
- {
- any(rhs).swap(*this); //先创建一个临时对象any(rhs),再调用下面的swap函数进行底层
- //数据交换,注意与*this交换数据的是临时对象,所以rhs的底层
- //数据并未被更改,只是在swap结束后临时对象拥有了*this的底
- //层数据,而此时*this也拥有了临时对象构造时所拥有的rhs的数
- //据的副本。然后临时对象由于生命期的结束而被自动析构,*this
- //原来的底层数据随之烟消云散。
- return *this;
- }
- any & swap(any & rhs) //swap函数,交换底层数据
- {
- std::swap(content, rhs.content); //只是简单地将两个指针的值互换
- return *this;
- }
- ~any() //析构函数
- {
- delete content; //释放容器,用的是基类指针,这就是placeholder需要一个虚
- //析构函数的原因
- }
- ...
- };
这虽然并非any的全部源代码,但是所有重要的思想已经表露无遗。剩下的部分只是一些简单的细节,请参见boost库的原文件。
“但是等等!”,你急切的说:“你失去了型别的信息。”唔...的确,当赋值的模板函数返回后你也就失去了关于型别的信息。考虑下面你可能想要写出的代码:
- int i=10;
- boost::any anyVal=i;
- int j=anyVal; //error,实际上你是想把anyVal赋给另一个int型变量,这应该以某种方式被允
- //许,但决不是在any类中提供转换操作符,因为你事先并不知道要用anyVal来承
- //载何种型别的变量,所以转换操作符无从给出。
当转换操作符的设想彻底失败后,我们只能借助于某些“外来”的显式转换操作。就向static_cast<>一样。Boost提供了any_cast<>,于是你可以这样写:
- int j=any_cast<int>(anyVal);
事实上,any_cast的代码是这样的:
- template<typename ValueType>
- ValueType any_cast(const any & operand)
- {
- const ValueType * result = any_cast<ValueType>(&operand);//调用any_cast针对指针的版
- //本。
- if(!result) //如果cast失败,即实际 保存的并非ValueType型数据,则抛出一个异常
- throw bad_any_cast(); //派生自std::bad_cast
- return *result;
- }
而any_cast针对指针的版本是这样:
- template<typename ValueType>
- ValueType * any_cast(any * operand)
- {
- return operand && operand->type() == typeid(ValueType) //这个型别检查很重要,后面会
- //对它作更详细的解释
- 1 ? &static_cast<any::holder<ValueType> *>(operand->content)->held:0; //这儿有个向下
- //型别转换
- }
这将通过编译,且运行期通常竟然也不会出错,下面我为你解释为什么会这样。
boost::anyVal=i;其实将anyVal.content指针指向了一个holder对象(请回顾上面的代码)。然后any_cast(anyVal)实际上调用了any_cast<>针对指针的重载版本,并将anyVal的地址传递过去,也就是转到1处,因为调用的是any_cast,所以1处的代码被编译器特化为
- 2 static_cast<any::holder<double> *>(operand->content)->held
但是前面说过,operand->content实际指向的是any::holder,所以这个static_cast<>是“非法”的,然而事实是:它能通过编译!原因很简单,holder和holder都是placeholder的基类。将基类指针向派生类指针转换被认为是合法的。但这却酿成大错,因为表达式2的型别将因此被推导为double!原先holder只给int held;成员分配了sizeof(int)个字节的内存,而现在却要将int型的held当作double型来使用,也就是说使用sizeof(double)个字节内存。所以这就相当于:
- int i=10;
- double* pd=(double*)(void*)&i;
- double d=*pd; //行为未定义,但通常却不会出错,然而隐藏的错误更可怕,你得到的d的值几
- //乎肯定不是你想要的。
使用typeinfo让我们有可能在运行时发现这种型别不符并及时抛出异常。但有个违反直观的事情是上面的那行错误的代码仍能通过编译,并且你也无法阻止它通过编译,因为holder和holder都是placeholder的基类。所以只能期望程序员们清楚自己在做什么,要不然就给他个异常瞧瞧。
使用boost::any实现virtual template成员函数
如你所知,C++中没有提供virtual template function。然而有时候你的确会有这种需要,any可以一定程度上满足这种需要,例如,
- class Base
- {
- public:
- virtual void Accept(boost::any anyData)
- {
- ...
- }
- };
- class Derived:public Base
- {public:
- virtual void Accept(boost::any anyData)
- {
- ...
- }
- };
这样的Accept函数能够接受任意类型的数据,并且是virtual函数
http://blog.csdn.net/passion_wu128/article/details/38514165
Boost源码剖析之:泛型指针类any的更多相关文章
-
boost源码剖析----boost::any
boost源码剖析----boost::any 有的时候我们需要有一个万能类型来进行一些操作,这时候boost::any就派上用场了. boost::Any testInt(10); int val ...
-
boost.asio源码剖析(五) ---- 泛型与面向对象的完美结合
有人说C++是带类的C:有人说C++是面向对象编程语言:有人说C++是面向过程与面向对象结合的语言.类似的评论网上有很多,虽然正确,却片面,是断章取义之言. C++是实践的产物,C++并没有为了成为某 ...
-
WorldWind源码剖析系列:挂件类Widgets
WorldWindow用户定制控件类中所包含的的挂件类Widgets控件主要有如下图所示的派生类.它们的类图如下所示. 鉴于挂件类Widgets及其派生类,相对简单,基本上都是些利用DirectX3D ...
-
WorldWind源码剖析系列:设置类SettingsBase
PluginSDK中的星球设置类WorldSettings 和WorldWind.程序设置类WorldWindSettings均继承自父类SettingsBase.类图如下所示.其中父类Setting ...
-
WorldWind源码剖析系列:角度类Angle
PluginSDK中的角度结构体Angle类图如下所示. 角度结构体主要定义了一个弧度表示角度值的字段:double Radians.还有几个表示角度最大值.最小值.非数值和零角度等字段.定义了一个D ...
-
WorldWind源码剖析系列:BMNG类构造函数深入分析
BMNG构造函数深入分析 一.主要类图 二.主要功能: 1) BMNG类 BMNG类将包含以“Blue Marble”为主题的所有可渲染影像的根节点添加到当前星球的可渲染对象列表中,包括 ...
-
WorldWind源码剖析系列:插件类Plugin、插件信息类PluginInfo和插件编译器类PluginCompiler
插件类Plugin是所有由插件编译器加载的插件子类的抽象父类,提供对插件的轻量级的访问控制功能. 插件信息类PluginInfo用来存储关于某个插件的信息的类,可以理解为对插件类Plugin类的进一步 ...
-
WorldWind源码剖析系列:日志类Log
Utility工程中的日志类Log主要用来输出Debug状态下的调试信息.该类的类图如下: 日志类Log中使用到的类和内嵌结构体类型主要有以下这些: public class LogEventArgs ...
-
WorldWind源码剖析系列:缓冲类Cache
缓冲类Cache主要用于在最小的限制条件下保存从远程服务器通过网络下载下来的地理空间数据,以便当用户处于离线状态时能够使用这些已经缓冲好的数据.Google Earth也采用类似机制处理用户离线浏览漫 ...
随机推荐
-
CenOS下搭建VPN服务
公司生产环境使用的是阿里云主机,采用的是两台nginx主机进行反向代理,现在需要内网一台服务器能够访问公网,所以在nginx服务器上搭建了VPN服务,用于进行内网访问公网. 系统环境:CenOS 6. ...
-
Eclipse “Invalid Project Description” when creating new project from existing source
1) File>Import>General>Existing Project into Workspace2) File>Import>Android>Exist ...
-
Mysql视图的作用及其性能分析
定义:视图是从一个或几个基本表导出的表,它与基本表不同,是一个虚表. 作用: 1.简化操作,不用进行多表查询. 2.当不同种类的用用户共享同一个数据库时,非常灵活,(用户以不同的 方式看待同一数据. ...
-
【工具篇】notepad++
一直都是使用notepad++来写程序,感觉比UE好用多了,没有UE那么重.前段时间网上还做了一个调查,notepad++在国外使用排行还是非常高的,在国内也是很流行.以下记录一些比较实用的技巧. 一 ...
-
C#委托(Delegate)学习日记
在.NET平台下,委托类型用来定义和响应应用程序中的回调.事实上,.NET委托类型是一个类型安全的对象,指向可以以后调用的其他方法.和传统的C++函数指针不同,.NET委托是内置支持多路广播和异步方法 ...
-
[ext4]13 空间管理 - Prealloc分配机制
作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 在ext4系统中,对于小文件和大文件的空间申请请求,都有不同的分配策略 ...
-
最详细的PHP flush()与ob_flush()的区别详解
buffer ---- flush()buffer是一个内存地址空间,Linux系统默认大小一般为4096(1kb),即一个内存页.主要用于存储速度不同步的设备或者优先级不同的 设备之间传办理数据的区 ...
- 【MySQL】查看支持的字符集show character set;
-
SSM-Spring-17:Spring中aspectJ注解版
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- AspectJ AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP 语法,能够在编译期提供 ...
-
js,html-点击直接跳转到页面底/顶部
案例一:js控制,无滑动效果 <html> <body> <a href="javascript:void(0);" onclick="ja ...