POCO C++库学习和分析 -- 数据类型转换
文章写到这里,Foundation库中的功能已经介绍过半了。在接下去介绍其他模块之前,我们先来回顾一下前面的内容。前面的内容包括了:
1. SharedLibrary模块(插件技术) 《Foundation库SharedLibrary模块分析》
2. 线程(锁,线程,线程池,定时器,任务,主动对象) 《线程》
3. 内存管理(智能指针,内存池,自动释放的对象池,对象工厂) 《内存管理》
4. 进程(进程,进程通讯) 《进程》
5. 消息和事件(同步/异步消息传递,消息队列) 《通知和事件》
有了这些模块,我们就可以搭建起一个本地程序的框架了(当然这不包括绘图和显示,Poco库不提供这些功能)。程序的框架很重要,就如同人的骨架和血液一样,决定了一个程序的结构,间接的影响了程序的可修改性和可维护性,但这还不够,要写出一个完整的程序,我们还需要其他的一些部分,这些部分也很重要,就如同人的肌肉和衣服。
下面介绍Foundation库中关于转换的几个类: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:将高序字节存储在起始地址(高位字节存储在内存中低位地址)。
在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
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 *保存。读取时再有程序员判断其类型。这样效率虽最高但无法保证类型安全
下面是Poco::Any的一个例子:
#include "Poco/Any.h"
#include "Poco/Exception.h"
#include
using Poco::Any;
using Poco::AnyCast;
using Poco::RefAnyCast;
int main(int argc, char** argv)
{
Any any(42);
int i = AnyCast(any); // okay
int& ri = RefAnyCast(any); // okay
try
{
short s = AnyCast(any); // throws BadCastException
assert(any.type() == typeid(int));
}
catch (Poco::BadCastException&)
{}
return 0;
}
最后给出Poco::Any的类图
![](http://img.e-com-net.com/image/info8/fe9e59cc7a954adf954a172bf8b89d25.jpg)
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
第二,在有限类型内部,Poco::DynamicAny提供函数完成与Poco::Any类类似的功能。事实上DynamicAny::extract()函数和Any类的友元函数AnyCast()是基本一致的。下面是二者代码:
template 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* pHolderImpl = static_cast*>(_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
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(pAny).
/// Will return NULL if the cast fails, i.e. types don't match.
{
return operand && operand->type() == typeid(ValueType)
? &static_cast*>(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的类图:
![](http://img.e-com-net.com/image/info8/feee9ac22f2e465386078e8ad1e9ca63.jpg)
在介绍完这个类之前提一句,有兴趣的同学也可以去考察一下boost库中的boost::variant和boost::lexical_cast,看一看它们和Poco::DynamicAny的异同。
(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8638475)