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

刘未鹏

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

动机

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,下面我就为你讲述它的原理及构造。

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源码剖析》系列文章)

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

  1. boost源码剖析之:泛型函数指针类boost::function(rev#3)

    boost源码剖析之:泛型函数指针类boost::function(rev#3) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba)   Note: 并非新作,03年曾放 ...

  2. boost源码剖析之:多重回调机制signal(下)

    boost源码剖析之:多重回调机制signal(下) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) 在本文的上篇中,我们大刀阔斧的剖析了signal的架构.不过还有 ...

  3. boost源码剖析之:多重回调机制signal(上)

    boost源码剖析之:多重回调机制signal(上) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) boost库固然是技术的宝库,却更是思想的宝库.大多数程序员都知 ...

  4. boost源码剖析之:Tuple Types(rev#2)

    boost源码剖析之:Tuple Types(rev#2)   刘未鹏(pongba) C++的罗浮宫(http://blog.csdn.net/pongba)   Note: 并非新作,04年曾放在 ...

  5. boost源码剖析之:boost::multi_array

    boost源码剖析之:boost::multi_array 谢轩 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) Note: 并非新作,是以前和老朋友谢轩写的,也可以 ...

  6. boost源码剖析之:泛型编程精灵type_traits(rev#2)

    boost源码剖析之:泛型编程精灵type_traits(rev#2) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) 动机 使用traits的动机一般有三种,分派. ...

  7. java.lang 源码剖析_java.lang.Void类源码解析

    在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code ...

  8. Android-源码剖析CountDownTimer(倒计时类)

    简介 CounterDownTImer是Android系统自带的一个倒计时器,特别是在做app登录时会比较有用. 用法 非常简单,比如做个倒计时60s且每隔1s会刷新一下,可以这样写 new Coun ...

  9. Boost源码剖析之:容器赋值-assign

    相信大多数使用STL的人都是为了使用里面的容器,使用vector.list.map的程序员对以下代码可以说是非常熟悉了: vector i_v; i_v.push_back(1); i_v.push_ ...

最新文章

  1. 第十四课.马尔科夫链
  2. 记事本保存的内容被覆盖_记事本的妙用——双击记事本即可关机
  3. 浅析人工智能的数学基础(文末送书!)
  4. xml的方式配置AOP:Aspect Oriented Programming
  5. LoadRunner远程监测Centos服务性能配置过程
  6. 容器编排技术 -- Kubernetes kubectl 与 Docker 命令关系
  7. TensorFlow笔记(12) VGG16
  8. 网络流20+4题解题报告(已更前20题)
  9. 蚂蚁财富号:大、小型基金公司的电商业务对比
  10. python仓库管理
  11. Qt网络编程01-QTcpSocket和QTcpServer的基本使用
  12. HoloLens2开发笔记-使用UWP原生MediaCapture解决图像捕获与WebRTC视频流冲突问题
  13. UE4中的玩家类UPlayer、ULocalPlayer 和 UNetConnection
  14. ITK 形态学中的开运算和闭运算 腐蚀 膨胀
  15. 计算机组成原理~计算机系统简介①
  16. 运维 ---linux基础
  17. (STM32笔记)一、STM32特点
  18. CocosCreator摄像机使用相关经验整理
  19. 《阿里管理红宝书》读后感
  20. 看完这个文章就可以对 ArrayList 有自己的见解了

热门文章

  1. 关于第5周反向传播算法的一些争论与思考
  2. jvm性能调优实战 - 48无限循环调用和没有缓存的动态代理引起的OOM
  3. jvm性能调优实战 - 29使用 jstat 摸清线上系统的JVM运行状况
  4. Java 8 - CompletableFuture组合式异步编程
  5. Android5.0新特性-Material Design
  6. C++五子棋(四)——走棋原理及权值计算
  7. 使用Visual Studio 2019开发Qt程序
  8. oracle 11g autotrace,ORACLE 使用AUTOTRACE功能
  9. Maven安装和配置及使用方法
  10. 低差异序列:范德科皮特序列(Van der Corput sequence)