荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: bso (BSO's So Open), 信区: Program
标  题: Boost源码剖析之:泛型指针类any之海纳百川
发信站: 荔园晨风BBS站 (Wed Aug 16 10:48:27 2006), 站内

作者:ppLiu(刘未鹏)

C++是强类型语言,所有强类型语言对型别的要求都是苛刻的,型别一有不合编译器就会抱
怨说不能将某某型别转换为某某型别,当然如果在型别之间提供了转换操作符或是标准所允
许的一定程度的隐式转换(如经过非explicit构造函数创建临时变量的隐式转换或是在int,
long这些基本型别间的)又另当别论。总的说来,为了保持型别安全,C++有严厉的要求。然
而有时候程序员可能有这样的需要:

int i;
long 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;
 //这儿有个向下
        //型别转换
}这两个any_cast版本应该很好理解。后版本中的型别检查是必要的,如果没有这个检查,
考虑以下代码:
int i=10;
boost::any anyVal=i;
double d=any_cast<double>(anyVal); //如果没有那个型别检查,这将通过编译且运行期
通常也不
//会出错,但是
//对d的赋值将会是非常奇怪的情形。这将通过编译,且运行期通常竟然也不会出错,下面
我为你解释为什么会这样。
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函数


--
  →  小白脸公司正式成立.....en.....来吧  ←
    →玩游戏?炒股?灌水?---->>>>>梦想农庄欢迎各位的光临!!!!!!←
↘                                          ↙
    →http://192.168.116.111/LTM/
 【 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  】


※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.116.111]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店