C++ std::any的模擬實現(xiàn)
std::any
std::any是C++標準庫中的一個類,官網(wǎng)對它的描述如下:
類 any 描述用于任何可拷貝構造類型的單個值的類型安全容器。
類 any 的對象存儲任何滿足構造函數(shù)要求的類型的一個實例或為空,而這被稱為 any 類對象的狀態(tài)。存儲的實例被稱作所含對象。若兩個狀態(tài)均為空,或均為非空且其所含對象等價,則兩個狀態(tài)等價。
非成員 any_cast 函數(shù)提供對所含對象的類型安全訪問。
換句話說,std::any對象可以存儲任何類型的數(shù)據(jù)(單例等特殊情況除外)。這篇文章來探討一下如何自己實現(xiàn)一個Any類。
Any的基本原理
在C++這種強類型語言中,想用一種類型來保存多種類型的數(shù)據(jù),首先想到的就是用父類指針(或引用)來保存子類,實現(xiàn)運行時多態(tài)。但問題是,我們想要保存任意類型,必須使所有類型都有一個公共的父類。在某些語言(如Java)中,有一個Object類,是所有類的父類,因此這種語言中就非常容易實現(xiàn)。但C++的類型系統(tǒng)相當混亂,原生類型沒有父類,STL的類型也沒有一個公共父類,而自定義類型也不會自動繼承自一個公共父類,因此直接用父類指針不可行。但是如果我們把模板和繼承結合一下就可以了,為每一種類型創(chuàng)建一個對應的模板類,這個模板類又繼承自一個父類。核心代碼如下:
class AnyHelperBase { }; template<typename T> class AnyHelper :public AnyHelperBase { T data; };
這樣我們就可以用AnyHelperBase*
類型來存儲任意類型的數(shù)據(jù)了。當然,這只是大體思路,還需要具體完善。下面我們將以上述代碼為母體,添加功能。
將數(shù)據(jù)存儲到Any
Any類
在上面的代碼中,如何將數(shù)據(jù)存儲到Any?肯定需要一個AnyHelperBase*
的類型。但考慮到直接操作指針不是很方便,并且std::any使用的時候并不需要指針,我們應該再寫一個類來維護AnyHelperBase*
。
class Any { private: class AnyHelperBase { public: }; template<typename T> class AnyHelper :public AnyHelperBase { public: T data; }; AnyHelperBase* data; public: };
構造函數(shù)
接下來實現(xiàn)AnyHelper的構造函數(shù)(第一個是就地構造,直接通過參數(shù)構造data,后兩個是拷貝構造):
template<typename ...Args> AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {} AnyHelper(const AnyHelper& other) :data(other.data) {} AnyHelper(const T& value) :data(value) {}
Any類的構造函數(shù):
Any() :data(nullptr) {} template<typename T> Any(const T& value) : data(new AnyHelper<std::decay_t<T>>(value)) {} //Any(const Any& other) :data( ??? ) {} Any(Any&& other) :data(other.data) { other.data = nullptr; }
注意:std::decay_t<T>
的作用是去掉T的const,引用等亂七八糟的屬性,比如std::decay_t<const int&>
的結果是int
。例如,我們顯然不希望傳入const int
與int
得到不同的結果。這一點很重要,因為如果類型不匹配,后面獲取數(shù)據(jù)時就會拋出異常!
拷貝構造的困難和解決方案
在寫拷貝構造(上面代碼的第三個函數(shù))時,我們遇到了問題。由于是深拷貝,我們肯定不能直接復制指針,而是應該再new一個對象。但問題是,我們怎么獲取另一個Any中的類型呢?這個問題似乎不好解決,因為只有在AnyHelper類內(nèi)部我們才會知道存儲的類型(這句話很重要)。但我們可以變通一下,讓AnyHelper類直接返回一個自身的拷貝的指針,我們不必關心他具體是什么類型。當然,我們使用的是AnyHelperBase*
,所以AnyHelperBase類里必須就得有這個函數(shù),換句話說,這得是一個虛函數(shù)。在這里我們又用到了多態(tài)的特性。往AnyHelperBase和AnyHelper中添加Clone函數(shù):
class AnyHelperBase { public: virtual AnyHelperBase* Clone()const = 0; }; template<typename T> class AnyHelper :public AnyHelperBase { public: T data; template<typename ...Args> AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {} AnyHelper(const AnyHelper& other) :data(other.data) {} AnyHelper(const T& value) :data(value) {} virtual AnyHelper* Clone()const { return new AnyHelper(*this); } };
Any類的拷貝構造函數(shù):
Any(const Any& other) :data(other.data->Clone()) {}
賦值運算符
賦值運算符和構造函數(shù)基本一樣,需要注意的是delete原來的data。
template<typename T> Any& operator=(const T& value) { if (data != nullptr) delete data; data = new AnyHelper<std::decay_t<T>>(value); return *this; } Any& operator=(const Any& other) { if (data != nullptr) delete data; data = other.data->Clone(); return *this; } Any& operator=(Any&& other) { if (data != nullptr) delete data; data = other.data; other.data = nullptr; return *this; }
其他賦值類函數(shù)
注意到std::any可以有空值,并且可以設置為空,我們也寫一個Reset函數(shù)將Any設為空。
void Reset() { if (data != nullptr) delete data; data = nullptr; }
另外,為了優(yōu)化性能,并且支持一些不可移動和拷貝的類型,我們添加就地構造函數(shù),可以直接通過參數(shù)構造一個對象。
template<typename T, typename ...Args> std::decay_t<T>& Emplace(Args&&... args) { if (data != nullptr) delete data; auto temp = new AnyHelper<std::decay_t<T>>(std::forward<Args>(args)...); data = temp; return temp->data; }
還有一個簡單的Swap,直接交換data指針:
void Swap(Any& other) { AnyHelperBase* temp = this->data; this->data = other.data; other.data = temp; }
到這里,Any類就可以存儲數(shù)據(jù)了。
從Any獲取數(shù)據(jù):Any轉換為其他類型
對一個實用的Any類來說,獲取數(shù)據(jù)也是必不可少的,實現(xiàn)獲取數(shù)據(jù)即將Any轉換為其他類型。對std::any來說,有std::any_cast函數(shù)來實現(xiàn)這一轉換,我們也寫一個AnyCast函數(shù)。
template<typename T> T AnyCast(const Any& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> T AnyCast(Any& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> T AnyCast(Any&& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> const T* AnyCast(const Any* any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data); if (p == nullptr) return nullptr; return &p->data; } template<typename T> T* AnyCast(Any* any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data); if (p == nullptr) return nullptr; return &p->data; }
AnyCast一共有5個重載(和STL中的一致),前三個是一組,后兩個是一組。前三個的特性是轉換失敗會拋出異常,后兩個接受指針,返回指針,失敗不會拋異常,而是會返回空指針。5個函數(shù)實現(xiàn)原理都是一樣的,核心就是使用dynamic_cast將AnyHelperBase*
類型的data轉換為相應的AnyHelper子類。dynamic_cast是向下轉換的操作符,也支持運行時多態(tài),如果轉換失敗會返回空指針。
獲取Any信息
到現(xiàn)在,一個Any類的核心功能已經(jīng)全部完成,不過為了模擬std::any,我們還是再添加一些獲取信息的函數(shù)。
獲取類型的type_info
我們前面說過,在我們實現(xiàn)的Any類中,只有在AnyHelper類內(nèi)部我們才會知道存儲的類型。因此,獲取類型必須從AnyHelper類下首。類似于Clone,我們再為AnyHelperBase和AnyHelper添加一個虛函數(shù):
class AnyHelperBase { public: virtual const std::type_info& Type()const = 0; virtual AnyHelperBase* Clone()const = 0; }; template<typename T> class AnyHelper :public AnyHelperBase { public: T data; //構造函數(shù)省略 //... virtual const std::type_info& Type()const { return typeid(T); } virtual AnyHelper* Clone()const { return new AnyHelper(*this); } };
這樣Any類的Type就好寫了:
const std::type_info& Type()const { return data->Type(); }
HasValue
沒啥好說的…
bool HasValue()const { return data != nullptr; }
析構函數(shù)
在這里我提醒一下大家,雖然析構函數(shù)很簡單,但一定不要忘了寫,否則會引起內(nèi)存泄漏!檢查析構函數(shù)是一個很好的代碼習慣!
~Any() { if (data != nullptr) delete data; }
附錄:完整代碼
到這里,整個Any類就完成了。下面是完整代碼:
namespace MyStd { class Any { private: class AnyHelperBase { public: virtual const std::type_info& Type()const = 0; virtual AnyHelperBase* Clone()const = 0; }; template<typename T> class AnyHelper :public AnyHelperBase { public: T data; template<typename ...Args> AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {} AnyHelper(const AnyHelper& other) :data(other.data) {} AnyHelper(const T& value) :data(value) {} virtual const std::type_info& Type()const { return typeid(T); } virtual AnyHelper* Clone()const { return new AnyHelper(*this); } }; template<typename T> friend T AnyCast(const Any& any); template<typename T> friend T AnyCast(Any& any); template<typename T> friend T AnyCast(Any&& any); template<typename T> friend const T* AnyCast(const Any* any); template<typename T> friend T* AnyCast(Any* any); AnyHelperBase* data; public: Any() :data(nullptr) {} template<typename T> Any(const T& value) : data(new AnyHelper<std::decay_t<T>>(value)) {} Any(const Any& other) :data(other.data->Clone()) {} Any(Any&& other) :data(other.data) { other.data = nullptr; } const std::type_info& Type()const { return data->Type(); } bool HasValue()const { return data != nullptr; } void Reset() { if (data != nullptr) delete data; data = nullptr; } template<typename T> Any& operator=(const T& value) { if (data != nullptr) delete data; data = new AnyHelper<std::decay_t<T>>(value); return *this; } Any& operator=(const Any& other) { if (data != nullptr) delete data; data = other.data->Clone(); return *this; } Any& operator=(Any&& other) { if (data != nullptr) delete data; data = other.data; other.data = nullptr; return *this; } void Swap(Any& other) { AnyHelperBase* temp = this->data; this->data = other.data; other.data = temp; } template<typename T, typename ...Args> std::decay_t<T>& Emplace(Args&&... args) { if (data != nullptr) delete data; auto temp = new AnyHelper<std::decay_t<T>>(std::forward<Args>(args)...); data = temp; return temp->data; } ~Any() { if (data != nullptr) delete data; } }; template<typename T> T AnyCast(const Any& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> T AnyCast(Any& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> T AnyCast(Any&& any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any.data); if (p == nullptr) throw std::runtime_error("Bad any cast!"); return p->data; } template<typename T> const T* AnyCast(const Any* any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data); if (p == nullptr) return nullptr; return &p->data; } template<typename T> T* AnyCast(Any* any) { auto p = dynamic_cast<Any::AnyHelper<std::decay_t<T>>*>(any->data); if (p == nullptr) return nullptr; return &p->data; } }
到此這篇關于C++ std::any的模擬實現(xiàn)的文章就介紹到這了,更多相關C++ std::any內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++中靜態(tài)成員函數(shù)訪問非靜態(tài)成員的實例
這篇文章主要介紹了C++中靜態(tài)成員函數(shù)訪問非靜態(tài)成員的實例的相關資料,需要的朋友可以參考下2017-07-07