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類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM