刘未鹏|C++的罗浮宫

Knowledge sharing is the best reuse

原创 boost源码剖析之:泛型指针类any之海纳百川(rev#2)收藏

boost源码剖析之:泛型指针类any之海纳百川(rev#2)

 

刘未鹏

C++的罗浮宫(http://blog.csdn.net/pongba)

 

动机

C++是强类型语言,所有强类型语言对类型的要求都是苛刻的,类型一有不合编译器就会抱怨说不能将某某类型转换为某某类型,当然如果在类型之间提供了转换操作符或是标准所允许的一定程度的隐式转换(如经过非explicit构造函数创建临时变量的隐式转换或是在intlong这些基本类型间的)又另当别论。总的说来,为了保持类型安全,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,下面我就为你讲述它的原理及构造。

 

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:

// 虚析构函数,为保证派生类对象能用基类指针析构

virtual ~placeholder(){}

 

public:

  // 提供关于类型的信息

virtual const std::type_info & type() const = 0;

virtual placeholder * clone() const = 0;  // 复制

}; // placeholder

 

template<typename ValueType>

class holder : public placeholder

{

public:

holder(const ValueType & value)

: held(value)

{}

public:

virtual const std::type_info & type() const

{

  // typeid返回std::typeinfo对象引用,后者包含任意对象的类型信息, name,此外还提供operator==操作符你可以用typeid(oneObj)==typeid(anotherObj)来比两个对象之类型是否一致。

return typeid(ValueType); 

}

 

virtual placeholder * clone() const

{

return new holder(held);  // 改写虚函数,返回自身的复制体

}

 

public:

ValueType held; // 数据保存的地方

}; // holder

 

// 指向泛型数据容器holder的基类placeholder的指针

placeholder * content;

 

//模板构造函数,动态分配数据容器并调用其构造函数

template<typename ValueType>

any(const ValueType & value)

: content(new holder<ValueType>(value))

{}

...

// 与模板构造函数一样,但使用了swap惯用手法

template<typename ValueType>

any & operator=(const ValueType & rhs)

{

// 先创建一个临时对象any(rhs),再调用下面的swap函数进行底层数据交换,注意与*this交换数据的是临时对象,所以rhs的底层数据并未被更改,只是在swap结束后临时对象拥有了*this的底层数据,而此时*this也拥有了临时对象构造时所拥有的rhs的数据的副本。然后临时对象由于生命期的结束而被自动析构,*this原来的底层数据随之烟消云散。

any(rhs).swap(*this);

return *this;

}

 

any & swap(any & rhs) //swap函数,交换底层数据

{

std::swap(content, rhs.content); // 只是简单地将两个指针的值互换

return *this;

}

 

~any()  //析构函数

{

  //释放容器,用的是基类指针,这就是placeholder需要一个虚析构函数的原因

delete content;

}

...

};

 

这虽然并非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)

{

  // 调用any_cast针对指针的版本。

const ValueType * result = any_cast<ValueType>(&operand);

 

// 如果cast失败,即实际 保存的并非ValueType型数据,则抛出一个异常。

if(!result)

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; // 这儿有个向下类型转换

}

 

 

这两个any_cast版本应该很好理解。此外后一个版本中#1处的类型检查也是必要的,如果没有这个检查,考虑以下代码:

 

int i=10;

boost::any anyVal=i;

 

//如果没有那个类型检查,这将通过编译且运行期通常也不会出错,但是对d的赋值将会是非常奇怪的情形。

double d=any_cast<double>(anyVal);

 

这将通过编译,且运行期通常竟然也不会出错,下面我为你解释为什么会这样。

 

boost::anyVal=i;其实将anyVal.content指针指向了一个holder<int>对象(请回顾上面的代码)。然后any_cast<double>(anyVal)实际上调用了any_cast<>针对指针的重载版本,并将anyVal的地址传递过去,也就是转到#1处,因为调用的是any_cast<double>,所以#1处的代码被编译器实例化为:

 

// #2

static_cast<any::holder<double> *>(operand->content)->held

 

但是前面说过,operand->content实际指向的是any::holder<int>,所以这个static_cast非法的,然而事实是:它能通过编译!原因很简单: holder<double>placeholder的派生类,而operand->content的类型正是placeholder。从基类指针到派生类指针的转换被认为是合法的。但这却酿成大错,因为表达式#2的类型将因此被推导为double!原先holder<int>只给int held;成员分配了sizeof(int)个字节的内存,而现在却要将int型的held当作double型来使用,也就是说使用sizeof(double)个字节内存。所以这就相当于:

 

int i=10;

double* pd=(double*)(void*)&i;

 

// 行为未定义,但通常却不会出错,然而隐藏的错误更可怕,你得到的d的值几乎肯定不是你想要的。

double d=*pd;

 

使用typeinfo让我们有可能在运行时发现这种类型不符并及时抛出异常。但有个违反直观的事情是上面的那行错误的代码仍能通过编译,并且你也无法阻止它通过编译,因为holder<int>holder<double>都是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函数。

 

目录(展开boost源码剖析》系列文章)

 

发表于 @ 2004年08月24日 01:42:00|评论(loading...)

新一篇: .NET Generics vs. C++ Templates | 旧一篇: 在C++中侦测内嵌型别的存在(rev#2)

用户操作
[即时聊天] [发私信] [加为好友]
刘未鹏
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
刘未鹏的公告
除非特别声明,本站采用Creative Commons License许可。转载请保留作者、出处。非商业。

重要公告

本博客已经迁移至 http://mindhacks.cn ,此处保留作为镜像,但不保证一定同步更新所有内容。原订阅 http://blog.csdn.net/pongba/rss.aspx (原始 Feed) 的朋友请转为订阅永久 feed : http://mindhacks.cn/feed/

关于

我经常在 @TopLanguage | @Twitter | @Douban

《C++的罗浮宫》5年选集

——知识分享是最大的复用

下载地址:csdn资源频道|mediafire

讨论问题请到TopLanguage综合技术讨论组

TopLanguage

精彩言论@TopLanguage


pongba的共享阅读@Delicious


pongba@Twitter


pongba在读@豆瓣


gtalk/msn(邮件请发送到gmail邮箱)

pongba@gmail.com
pp_liu@msn.com

搜索(不要回车,点击Go)


pongba翻译的





这个Blog上都写了哪些东东

文章分类
收藏
C++
Andrei Alexandrescu
Andrew Lumsdaine
Bjarne Stroustrup
boost
C++ Standard Commitee
Doug Gregor
Hans J. Boehm
Jaakko Jarvi
Jeremy G. Siek
Matthew Wilson
newsgroups
boost.Developer
boost.User
comp.lang.c++.moderated
comp.std.c++
TopLanguage
Open Source
Ant
codeplex
Danga
Google AJAX Search API
Google Code Prettify
Google Web Toolkit
Hadoop
MS shared source initiative
notepad++
STLSoft
不认识的朋友们
Delphij
fatalerror99
flow with the life
Glacier
jimaxsoft
lifesinger@淘宝UED
Mr. 6
realazy
Robbin
SpiritEpic
TK
wuyizi
Yelz
丁丁虫
付翀
冰云
刘慈欣
卢昌海
吴欣安(atppp)
周爱民
和菜头
姬十三
守望轩
小花@BlogBus
林达华
浦宇平
白鸦
程化
罗浩|Startup Game
阮一峰
霍炬
飞之鸿
高远
鲍盛
机器学习/数据挖掘/信息检索/自然语言处理/认知科学/人工智能
AAAI
Apex
arXiv
Charles Kemp
Christopher Bishop
Christopher Manning
Cognitive Daily
Dan Jurafsky
David MacKay
ECML PKDD
Geoffrey Hinton
Herbert Simon
ICML
IJCAI
Jeff Hawkins
Jiawei Han
JMLR
Josh Tenenbaum
Larry Wasserman
Lucene
Marvin Minsky
MIT AI Lab
MIT Computational Cognitive Science Group
Mitchell Marcus
ML
NetLab
NIPS
Peter Norvig
Stanford AI Lab
Stanford NLP Lab
Stephen Boyd
Tom Mitchell
Trends in Cognitive Science
Vladimir Vapnik
Weka
Zhihua Zhou
技术
Coding Horror
High Scalability
Reddit
Stack Overflow
Steve Yegge
代码发芽网
淘宝UED团队
淘宝数据仓库团队
玩聚网
移山之道
其它
Gigapedia
Scientific American
Scientific American Mind
科学松鼠会
科幻世界
认识的朋友们
alai
chenyufei
dd
DreamHead
Googol
Jawley
Joyfire
littlestone
lxwde
Matrix67
realfun
RiceBall
roofalison
soloist
Tinyfool
windstorm
YongSun
书剑
云风
余晟
元凯宁
冯大辉(Fenng)
刘新宇
刘江@图灵
史晓明
吴新雨
周星星
周筠@博文视点
孟岩
张志强|阅微堂
张振
徐宥|4G Spaces
方舟@博文视点
曾登高
李笑来|Pure Pleasure
杨军
杨文博
熊节
王信文
王康生
苏杰@阿里巴巴
范怀宇
荣耀
莫华枫
蒋涛
袁泳(g9)|负暄琐话
许式伟
谢东升
谷文栋|Beyond Search
邹欣@MSRA
郑昀
阿朱
陈冀康@华章
陈怀兴
鲍志云
存档
Csdn Blog version 3.1a
Copyright © 刘未鹏