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

  在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类。
posted @ 2014-01-03 09:38  清清之雪  阅读(16806)  评论(3编辑  收藏  举报