boost::any實現解析

忘記當初具體想做什麼了,只記得要求:將各種類型(如int,char*,float,自定義類型等)放到一個容器裏。這樣的要求,目前stl的任何容器都是做不到的。因爲容器僅僅能夠存儲同一種類型的對象,因此我們只能往代理的方向去考慮。一提到代理,大家首先想到的可能是繼承:設計一個基類(如AnyType),其他的類型均從基類派生(如StringType,IntType,FloatType),容器裏存儲存儲基類指針,設計如下:

該種方案確實從一定程度上解決了方案,但卻不是一個很好的方案,原因如下:

1.擴展性很差,容器中想容納任何類型,都得從NullType派生一個新的類型,已做適配;

2.容器中存的是基類指針,涉及到動態管理內存,申請釋放內存又是令人頭大的問題。

那麼,還有沒有其他方案呢?有——模板類:設計一個模板類,讓模板類存儲實際對象,容器存儲模板對象,如下:

但是仔細一想,這供方案更令人沮喪。模板是編譯時多態,在編譯時AnyType<int>和AnyType<float>的具體類型在編譯時就已確定,它們屬於不同的類型,怎麼可能存在同一個容器裏呢?

既然單獨使用繼承和模板都不能完美的解決問題,那麼把二者結合起來呢:定義一個基類Content,用於定義指針,以便存儲;從Content派生模板類ContentImpl<T>,用於實現具體類型;AnyType存儲Content指針,並負責維護Content指針指向對象的初始化與銷燬,如下:

方案看似可行,接下來我們考慮下具體實現:允許構造空對象,默認構造函數不可少;需要用具體類型去構造一個AnyType對象,因此一個以具體類型對象作爲參數的構造函數不可少;具體對象的獲取,使用成員函數還是類型轉換(全局函數),個人覺得亮着都一樣,只不過使用成員函數看上去怪怪的,但使用全局函數不要忘記設置友元函數;具體對象返回指針還是引用,肯定是指針,通過nullptr可以判斷對象是否有效;拷貝構造函數;賦值函數......最後得出下面的結果:

class AnyType
{
public:
	AnyType(void) : m_ContentPtr(nullptr) {}

	template <typename T>
	AnyType(const T &val) : m_ContentPtr(new ContentImpl<T>(val)) {}

	AnyType(const AnyType &other) : m_ContentPtr(other.m_ContentPtr == nullptr ? nullptr : other.m_ContentPtr->Clone()) {}

	template <typename T, typename std::enable_if<false, std::is_same<AnyType &, T>>::value>
	AnyType(T &&val) : m_ContentPtr(new ContentImpl<typename std::decay<T>::type>(static_cast<T &&>(val))) {}

	AnyType(AnyType &&other) : m_ContentPtr(other.m_ContentPtr) { other.m_ContentPtr = nullptr; }

	~AnyType(void)
	{
		if (m_ContentPtr != nullptr) {
			delete m_ContentPtr;
			m_ContentPtr = nullptr;
		}
	}

	AnyType &operator=(const AnyType &rhs)
	{
		if (&rhs == this)
			return *this;

		delete m_ContentPtr;
		m_ContentPtr = rhs.m_ContentPtr->Clone();

		return *this;
	}
		
	bool IsNull(void) const { return m_ContentPtr == nullptr; }

	const std::type_info &Type(void) { return m_ContentPtr == nullptr ? typeid(void) : m_ContentPtr->Type(); }

	template <typename T>
	T *Get(void) { return m_ContentPtr == nullptr ? nullptr : m_ContentPtr->Type() == typeid(T) ? &static_cast<ContentImpl<T> *>(m_ContentPtr)->m_Value : nullptr; }

private:
	class Content
	{
	public:
		virtual ~Content(void) {}
		
		virtual const std::type_info &Type(void) = 0;

		virtual Content *Clone(void) = 0;


	};

	template <typename T>
	class ContentImpl final : public Content
	{
	public:
		ContentImpl(const T &val) : m_Value(val) {}

		ContentImpl(T &&val) : m_Value(static_cast<T &&>(val)) {}

		const std::type_info &Type(void) override { return typeid(m_Value); }

		Content *Clone(void) override {	return new ContentImpl(m_Value); }

		ContentImpl &operator=(const ContentImpl &) = delete;


	public:
		T m_Value;

	};

	Content *m_ContentPtr;

	template <typename T>
	friend T *AnyTypeCast(AnyType *operand);

};

template <typename T>
inline T *AnyTypeCast(AnyType *operand)
{
	return operand->Type() == typeid(T) ? &static_cast<AnyType::ContentImpl<T> *>(operand->m_ContentPtr)->m_Value : nullptr;
}

是不是很熟悉?其實這就是boost::any的設計思想。注意上面的代碼用到了c++14的特性,vs2017編譯能夠通過,其他開發環境未曾嘗試。注意構造函數AnyType(T &&val)的實現,必須在模板參數中添加“typename std::enable_if<false, std::is_same<AnyType &, T>>::value”,意思是如果類型模板類型T爲AnyType,則不允許調用該構造函數。如果沒有該句,使用AnyType非常量對象構造AnyType對象時,不會執行AnyType的拷貝構造函數,而執行AnyType(T &&val),具體原因不知道。如果讀者知道,歡迎留言。

發佈了27 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章