Boost源码剖析之:泛型指针类any
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也采用类似机制处理用户离线浏览漫 ...
随机推荐
- UVA 11461 - Square Numbers(水题)
题目链接 #include <cstdio> #include <cstring> #include <string> #include <cmath> ...
- My Notepad
I have spent near more two weeks to write this Notepad application. At this moment, I want to share ...
- 实现UITableView循环利用
tableViewUITableView循环利用 前言 大家都知道UITableView,最经典在于循环利用,这里我自己模仿UITableView循环利用,写了一套自己的TableView实现方案,希 ...
- 通过MongoDB的samus驱动实现基本数据操作
一.MongoDB的驱动 MongoDB支持多种语言的驱动: 在此我们只介绍 C# 的驱动.仅C#驱动都有很多种,每种驱动的形式大致相同,但是细节各有千秋,因此代码不能通用.比较常用的是官方驱动和sa ...
- HTTPS原理介绍
内容来源于:http://www.guokr.com/post/114121/ 自己做个备忘. 二.HTTPS到底安全吗? 这个答案是肯定的,很安全.谷歌公司已经行动起来要大力推广HTTPS的使用,在 ...
- mysql主从复制过滤
主从复制过滤: 配置文件中的[mysqld]块中: master:(考虑到即时点还原一般不在主过滤) binlog_do_db= #数据库白名单 binlog_ignore_db= #数据库白名单 s ...
- typeof操作符在javascript中运用时时页面上的操作数显示
typeof可以告诉我们它的操作数是一个字符串(string).数值(number).函数(function).布尔值(boolean)或对象(object). 1.字符串(string) alert ...
- Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds 解决方法
Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds. If the server requires ...
- SQL 参数化查询 应用于 Like
在sql 进行参数化查询的时候,使用like 语句和参数的时候,错误的写法: Participant like '%@Participant%' ,这样在数据库为解析为 '%'participant ...
- mimikatz不反弹读取密码
有些时候无法反弹shell执行mimikatz,虽然可以用procdump导出lsass的内存dump文件,之后本地获取明文密码,但多少有点麻烦,其实mimikatz也支持命令行直接导出 mimika ...