POCO C++庫學習和分析 -- 數據類型轉換



1. ByteOrder

         ByteOrder提供了一系列的靜態函數用於字節序的轉換。在使用這個類之前,讓我們先了解一下它所解決問題。它主要用來解決big-endian和litter-endian的問題。

1.1 big-endian和litter-endian

         big-endian和litter-endian指的是讀取存儲時的數據解釋方式。它們只和多字節類型的數據有關,比如說int,short,long型,而對單字節數據byte卻沒有影響。
                     litter-endian:將低序字節存儲在起始地址(低位字節存儲在內存中低位地址)。

                     big-endian:將高序字節存儲在起始地址(高位字節存儲在內存中低位地址)。


         舉個例子,int a = 0x01020304
         在BIG-ENDIAN的情況下存放爲:
                      字節號  0        1         2      3
                 數據    01      02      03    04
         在LITTLE-ENDIAN的情況下存放爲:
                     字節號  0         1         2     3
               數據    04        03      02    01


          再舉一個,int a = 0x1234abcd
         在BIG-ENDIAN的情況下存放爲:
                     字節號  0      1       2      3
               數據    12     34    ab    cd
         在LITTLE-ENDIAN的情況下存放爲:
                    字節號  0       1      2      3
              數據    cd      ab    34    12

1.2 主機序和網絡序

         主機序是讀取計算機內存數據時的解釋方式,它和CPU、操作系統的類型相關,分爲litter-endian和big-endian。X86架構的cpu不管操作系統是NT還是UNIX系的,都是小字節序,而PowerPC 、SPARC和Motorola處理器則很多是大字節序。下面是一張字節序和CPU、操作系統的關係表。

         處理器                      操作系統            字節排序
         Alpha                        全部                 Little endian
         HP-PA                      NT                    Little endian
         HP-PA                      UNIX                Big endian
         Intelx86                   全部                  Little endian (x86系統是小端字節序系統)
         Motorola680x()      全部                  Big endian
         MIPS                        NT                    Little endian
         MIPS                        UNIX                Big endian
         PowerPC                NT                    Little endian
         PowerPC                非NT                Big endian   (PPC系統是大端字節序系統)
         RS/6000                 UNIX                Big endian
         SPARC                   UNIX                 Big endian
         IXP1200                 ARM核心          全部 Little endian

1.3 主機序和網絡序和大頭小頭引起的問題

         如果在兩臺字節序不同的主機之間進行網絡通訊,大小字節序的問題就會出現。通常的做法是在小字節序一端的主機進行處理,小字節序的主機在發送數據前,轉換數據爲大字節序,而在接受時,把大字節序數據轉成小字節序。
         如果在字節序相同的兩臺機器之間進行通訊,可以不用考慮字節序問題。
         同樣的是在兩臺機器之間,用java語言編寫通訊程序時,可以不考慮字節序問題。JAVA字節序與網絡字節序都是big-endian.

1.4 ByteOrder靜態函數

         ByteOrder提供了一組靜態的Api去解決這個問題。
         1) IntXX flipBytes(IntXX value)
              字節翻轉排序,實現大小字節序的轉換
         2) IntXX toBigEndian(IntXX value)
              把數據從本機序轉成大字節序。如果本機序是本身就是大字節序,返回結果爲轉之前數據。
         3) IntXX toLittleEndian(IntXX value)
              把數據從本機序轉成小字節序。如果本機序是本身就是小字節序,返回結果爲轉之前數據。
         4) IntXX fromBigEndian(IntXX value)
              把數據從大字節序轉成本機序。如果本機序是本身就是大字節序,返回結果爲轉之前數據。
         5) IntXX fromLittleEndian(IntXX value)
              把數據從小字節序轉成本機序。如果本機序是本身就是小字節序,返回結果爲轉之前數據。
         6) IntXX toNetwork(IntXX value)
              把數據從本機序轉成網絡序。如果本機序是本身就是大字節序,返回結果爲轉之前數據。
         7) IntXX fromNetwork(IntXX value)
              把數據從網絡序轉成本機序。如果本機序是本身就是大字節序,返回結果爲轉之前數據。

              下面來看一個ByteOrder的例子:
#include "Poco/ByteOrder.h"
#include <iostream>
using Poco::ByteOrder;
using Poco::UInt16;
int main(int argc, char** argv)
{
#ifdef POCO_ARCH_LITTLE_ENDIAN
	std::cout << "little endian" << std::endl;
#else
	std::cout << "big endian" << std::endl;
#endif
	UInt16 port = 80;
	UInt16 networkPort = ByteOrder::toNetwork(port);
	return 0;
}

2. Any

         Poco中的Any類,來自於Boost庫中的Any類。Any類主要用於數據庫讀取時的數據保存和解析。它能夠將任意類型值保存進去,並能把任意類型值讀出來。Boost::any的作者認爲,所謂generic type有三個層面的解釋方法:
         1. 類似variant類型那樣任意進行類型轉換,可以保存一個(int)5進去,讀一個(string)"5"出來。在variant類型內部使用union實現,使用靈活但效率較低。
         2. 區別對待包含值的類型,保存一個(int)5進去,不會被隱式轉換爲(string)'5'或者(double)5.0,讀出來還是(int)5。這樣效率較高且類型安全,不必擔心ambiguous conversions
         3. 對包含值類型不加區別,例如把所有保存的值強制轉換爲void *保存。讀取時再有程序員判斷其類型。這樣效率雖最高但無法保證類型安全


         boost::any就選擇了第二層面的設計思路,它允許用戶將任意類型值保存進一個any類型變量,但內部並不改變值的類型,並提供方法讓用戶在使用時主動/被動進行類型判斷。關於Poco::Any的進一步描述和實現技巧,可以看劉未鵬大大的《boost源碼剖析之:泛型指針類any之海納百川》和hityct1大大的《boost::any的用法、優點和缺點以及源代碼分析》。

         下面是Poco::Any的一個例子:
#include "Poco/Any.h"
#include "Poco/Exception.h"
#include <assert>

using Poco::Any;
using Poco::AnyCast;
using Poco::RefAnyCast;

int main(int argc, char** argv)
{
	Any any(42);
	int i = AnyCast<int>(any);              // okay
	int& ri = RefAnyCast<int>(any);         // okay
	try
	{
		short s = AnyCast<short>(any);  // throws BadCastException
                assert(any.type() == typeid(int));  
	}
	catch (Poco::BadCastException&)
	{}
	return 0;
}

         最後給出Poco::Any的類圖



3. DynamicAny

         Poco::DynamicAny在generic type的處理思路上採用的是上述第一種和第二種思路的結合。
         首先它支持有限類型之間的自動類型轉換,可以保存一個(int)5進去,讀一個(string)"5"出來。所謂有限類型很好理解,因爲類型轉化的本質是對內存數據的不同解釋,如果轉化前的數據類型和轉化後的數據類型都是不定且無限,作爲類的書寫者,實在是不能想象的。而有限類型的轉化至少我們可以枚舉,而事實上這正是Poco::DynamicAny實現時所做的。Poco::DynamicAny支持Int8、Int16、Int32、Int64UInt8、UInt16、UInt32、UInt64、bool、float、double、char、std::string、long、unsigned long、std::vector<T>、DateTime、LocalDateTime、Timestamp類型之間的轉化。爲此Poco::DynamicAny提供了成員函數convert和operator T()函數去實現上述的功能。當轉換失敗的時候會拋出異常。
         第二,在有限類型內部,Poco::DynamicAny提供函數完成與Poco::Any類類似的功能。事實上DynamicAny::extract()函數和Any類的友元函數AnyCast()是基本一致的。下面是二者代碼:
template <typename T> const T& DynamicAny::extract() const
		/// Returns a const reference to the actual value.
		///
		/// Must be instantiated with the exact type of
		/// the stored value, otherwise a BadCastException
		/// is thrown.
		/// Throws InvalidAccessException if DynamicAny is empty.
{
	if (_pHolder && _pHolder->type() == typeid(T))
	{
		DynamicAnyHolderImpl<T>* pHolderImpl = static_cast<DynamicAnyHolderImpl<T>*>(_pHolder);
		return pHolderImpl->value();
	}
	else if (!_pHolder)
		throw InvalidAccessException("Can not extract empty value.");
	else
		throw BadCastException(format("Can not convert %s to %s.",
			_pHolder->type().name(),
			typeid(T).name()));
}

template <typename ValueType>
ValueType* AnyCast(Any* operand)
	/// AnyCast operator used to extract the ValueType from an Any*. Will return a 


pointer
	/// to the stored value. 
	///
	/// Example Usage: 
	///     MyType* pTmp = AnyCast<MyType*>(pAny).
	/// Will return NULL if the cast fails, i.e. types don't match.
{
    return operand && operand->type() == typeid(ValueType)
                ? &static_cast<Any::Holder<ValueType>*>(operand->_content)->_held
                : 0;
}

         看到這裏,我們實際上就明白了Poco::DynamicAny和Poco::Any的使用場景。對於用戶自建數據類型,毫無疑問只能使用Poco::Any類。而對於C++語言內置的數據類型,使用Poco::DynamicAny,因爲Poco::DynamicAny不僅對於內置數據類型提供了類似Poco::Any的接口,而且還提供了相互之間的轉換功能。
         在Poco::DynamicAny的實現上,使用了模板特化技術,用於在不同數據類型之間的轉換,關於這一點,也可以理解成枚舉。實質上就是說把程序員在不同數據間的轉換工作在Poco::DynamicAny類中先實現了一遍,程序員只需要直接調用Poco::DynamicAny就可以了。

         對於convert()/operator T()函數和extract()函數的區別如下:
              T convert()/operator T()/void convert(T& val)              const T& extract()
                返回一個拷貝                                                                 返回一個常量引用
                自動轉變類型                                                                 不會自動轉變類型
                比Any慢                                                                           同Any一樣快

         下面是Poco::DynamicAny的類圖:



         在介紹完這個類之前提一句,有興趣的同學也可以去考察一下boost庫中的boost::variant和boost::lexical_cast,看一看它們和Poco::DynamicAny的異同。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章