c++任意类型Any类的实现

来源:互联网 发布:暗黑法术工厂升级数据 编辑:程序博客网 时间:2024/05/17 11:59

在java或c#中,Object类型对象可以指向任意类型的变量,因为所有的类默认都从Object类继承。但是在c++中,没有类似Object类这样的类型,而很多时候,为了设计出通用的程序,往往需要类似于Object类型作为参数或者返回值。例如,在另一篇文章《c++实现反射类》中就用到了可以指向任意类型的Any类。

  在c或者c++中,可以指向任意类型的关键字就是无符号类型void*,任何一个对象都可以使用void*来指向。例如以下类:
复制代码
class A { privateint a;};class B { privatechar b;}; int i = 10;A a;B b;void* tmp = &i;tmp = &a;tmp = &b;
复制代码
上述程序不会有任何问题,如果是这样,是否还需要Any类型呢?void*是否可以解决一切问题?我们继续往下看,
void* any = &a;B* b = (B*)any;
这里,首先将类型A的变量赋值给any,然后又将any赋值给类型B的变量,而且毫无问题,编译器不会给出任何警告,如果你非常不幸的话,程序毫无问题的运行,直到某一天在关键时刻整个系统崩溃。看来,void*实在是太万能,以至于对它执行任意操作都不会有问题。很显然,这种使用方式极其不安全。另外,因为void*是指针,所指向的对象如果已经被释放,再使用any时就会出现问题,这种情况下,需要重新new一个相同的对象,使用any指向new的对象,不过这样的话需要自己管理指针,使用起来会非常麻烦。因此,我们另想办法来实现Any类型而不使用void*。
  首先,如果可以接受任意类型的数据,Any类首要的一个特性就是与类型无关。在c++中,想做到类型无关,第一选择必是使用模板。而且我们希望这样使用Any类型:
Any any(string("test"));Any any(10);A a;Any any = a;
上述使用方式中,要求在定义Any类型时,不会显式指定Any类型,这样的话,Any就不能定义成一个模板类,因此符合上述使用要求的Any类的定义大致如下:
复制代码
template<typename ValueType>Any(ValueType value) {    content_ = value;  // content_是Any类成员,保存数据副本}
  template<typename ValueType>
  Any& operator=(ValueType value) {
    Any(value).Swap(*this);
    return *this;
  }
复制代码
需要注意的是,=的重载为什么不使用content_ = value,有两个原因:
  1. 如果ValueType是Any类型,会陷入函数调用无穷递归;
  2. 如果content_里面包含需要释放的资源,直接赋值的话,之前的资源不会释放;
因此,写成如上形式,使用一个临时对象保存参数值,在执行完Swap语句后,临时对象会被销毁,参数值通过临时对象交换到this指针指向的对象,this指向对象被交换到临时对象中,随着临时对象的销毁,this指向对象之前拥有的资源也会被回收或者释放。使用这种方式很方便地完成赋值操作。
有了以上两个函数,就可以这样使用:
复制代码
A a;Any any(a);Any any(10);Any any = new A;Any any;any = a;
复制代码
  这样,通过使用函数模板,Any类型可以接受任意类型的变量。到这里,又有另外一个问题,Any中的content_应该定义成什么类型。在我们看来,content_同样可以接受任意类型变量,那么content_也应该定义成Any类型,但是这样会陷入定义无穷递归。如何避免这种情况在c++中经常遇到,就是定义content_为Any类型指针。但是单纯一个指针无法保存指向对象的数据,因此,需要再新建一个类似Any类型的类,这个类专门负责保存Any类型指向对象的数据副本。因为需要保存任意类型的数据,可以将其定义为模板类:
template<typename ValueType>class Holder { private:  ValueType held_;};
类Holder可以保存任意类型的数据,不过在使用Holder类时,需要显示指定模板参数,如下:
holder<int> i_holder;holder<string> str_holder;
然而Any类中content_变量在定义时没有显示指定类型,因为Any不是模板类,没有模板类型参数传递给content_。为了解决这种情况,再定义一个Holder的基类,如下:
复制代码
class PlaceHolder {public:  virtual ~PlaceHoder() {}};template<typename ValueType>class Holder : public PlaceHolder { public:  Holder(const ValueType& value) : held(value) {} private:  ValueType held_;};
复制代码
定义完Holder基类PlaceHolder,就可以将Any类中的content_定义成PlaceHolder类指针类型了,现在,Any类基本框架已经搭建完,目前Any类如下:
复制代码
class Any { public:  Any() : content_(NULL) {}  template<typename ValueType>  Any(const ValueType& value) : content_(new Holder<ValueType>(value)) {  }  ~Any() {    delete content_;  } private:  PlaceHolder* content_;};
复制代码
但是,这里还有一个问题(谢谢壮壮熊的提示),那就是如果这样使用:
Any a(1);Any b(a);

程序就会挂掉,因为b变量保存的是Any类型,即content_是Any类型,b在调用析构函数时调用delete content_语句,该语句又会调用content_的析构函数,因为这里的content_是Any类型,所以Any析构函数就陷入无穷递归调用。因此,这里,需要定义Any另外一个接受Any类型参数的构造函数:

Any(const Any& other) : content_(other.content_ ? other.content_->clone() : NULL) {}// Holder中的clone函数virtual PlaceHoder* clone() const {  return new Holder(held_);}

 

然而,毕竟Any类型只是作为中间媒介来保存和传递数据,最终还是需要将Any转换成相应类型的对象,因此必须定义如下函数:
复制代码
template<typename ValueType>ValueType* any_cast() {  if (content == NULL)    return NULL;  else    return static_cast<holder<ValueType> *>(content_)->held_;}
复制代码
到这里,Any类基本功能就已经具备了。但是,这时的Any类也存在上面void*提到的问题,即没有类型检查,可以将Any类型转换成任意类型。在c++中,有个高级的功能就是运行时类型识别(RTTI),其中可以使用typeid操作符获得指针或引用所指对象的实际类型,因此,在进行类型转换时可以比较Any中存储的类型是否与转换的类型符合,如果不符合则转换失败打印日志,如果符合则转换成功,这里可以根据具体应用来控制转换结果。
 
本文中需要注意学习的知识点:
  1. 模板编程;
  2. =重载实现;
  3. 模板类继承;
  4. 临时对象的使用;
  5. 类型识别以及类型转换;
注:
本文的Any类借鉴boost中Any的实现,对其实现过程进行了剖析,文中程序只是样例,如果使用的话请直接使用boost中的Any类。
0 0