您的位置:首页 > 编程语言 > C语言/C++

C++基本功之 对象序列化

2012-11-28 13:21 169 查看
稍微正式一点的应用都会用到对象的序列化/反序列化操作,常见的需求包括:

对象的持久存储。比如把一个对象存储到文件;在需要的时候,再把对象从文件中读出来;

对象的传递。比如要把一个对象通过管道,socket等任何手段传送到对端;

从数据构建对象。对象类型未知,但是我们可以从数据中构建一个对象出来。

我们现在来分析这些需求。看看C++如何有效地序列化/反序列化对象。下文中,凡提到序列化,都包含相应的反序列化。

一个对象的状态由其所有的内部成员状态决定。所以序列化对象其实可以等价于序列化其内部对象,递归地,其内部对象的序列化又可以归于构成这些内部对象的序列化。我们可以发现,到了最后,所有的对象构成都归于基本类型的序列化。

注意,这里的讨论并不适用于任何的资源管理类对象。

首先,我们需要的是最基本的支持,亦即对C++基本类型的序列化和反序列化;同时,由于标准库中的字符串类使用极为广泛,我们同样加入支持;最后,我们也加入对非格式的POD数据的支持,这样,据大多数的序列化需求应该可以得到满足。以下直接给出实现,其后是简单的说明,请仔细揣摩。

[cpp] view plaincopy
namespace persistent {
const long true_pattern = 0xF00DF00D;
const long false_pattern = 0xBAD0BAD0;
class serializer
{
public:
serializer (std::ostream& os) : _ostream(os) {}
void put_char(char value) { return put_basic_t<char>(value); }
void put_wchar(wchar_t value) { return put_basic_t<wchar_t>(value); }
void put_bool(bool value) { return put_basic_t<bool>(value); }
void put_short(short value) { return put_basic_t<short>(value); }
void put_long(long value) { return put_basic_t<long>(value); }
void put_llong(int64 value) { return put_basic_t<int64>(value); }
void put_double(double value) { return put_basic_t<double>(value); }
void put_string(const std::string& value) { return put_string_t<std::string>(value); }
void put_wstring(const std::wstring& value) { return put_string_t<std::wstring>(value); }
void put_raw(size_t length, const byte* buff)
{
put_long(static_cast<long>(length));
_ostream.write(reinterpret_cast<const char*>(buff),
static_cast<std::streamsize>(length));
if (_ostream.bad())
throw std::exception("stream_write");
}
void put_raw(const util::raw_buffer& buff)
{
_ostream.write(reinterpret_cast<const char*>(buff.operator const byte*()),
static_cast<std::streamsize>(buff.size()));
if (_ostream.bad())
throw std::exception("stream_write");
}
protected:
template<typename T> void put_basic_t(T value)
{
_ostream.write (reinterpret_cast<char*>(&value), sizeof(T));
if (_ostream.bad())
throw std::exception("stream_write");
}
template<> void put_basic_t(bool value)
{
long pattern = value? true_pattern: false_pattern;
put_long(pattern);
if (_ostream.bad())
throw std::exception("stream_write");
}
template<typename T> void put_string_t(const T& value)
{
long len = static_cast<long>(value.length() * sizeof(T::value_type));
put_long(len);
_ostream.write(reinterpret_cast<const char*>(value.data()), len);
if (_ostream.bad())
throw std::exception("stream_write");
}
private:
std::ostream& _ostream;
serializer& operator =(const serializer&);
};
class deserializer
{
public:
deserializer (std::istream& is) : _istream(is) {}
char get_char() { return get_basic_t<char>(); }
wchar_t get_wchar() { return get_basic_t<wchar_t>(); }
bool get_bool() { return get_basic_t<bool>(); }
short get_short() { return get_basic_t<short>(); }
long get_long() { return get_basic_t<long>(); }
int64 get_llong() { return get_basic_t<int64>(); }
double get_double() { return get_basic_t<double>(); }
std::string get_string() { return get_string_t<std::string>(); }
std::wstring get_wstring() { return get_string_t<std::wstring>(); }
util::raw_buffer get_raw()
{
size_t length = static_cast<size_t>(get_long());
if (_istream.eof())
throw std::exception("unexpected_eof");
util::raw_buffer value(length);
_istream.read(value.force_to<char*>(), static_cast<std::streamsize>(length));
if (_istream.bad())
throw std::exception("stream_read");
return value;
}
void get_raw(util::raw_buffer& buff)
{
if (_istream.eof())
throw std::exception("unexpected_eof");
_istream.read(buff.force_to<char*>(), static_cast<std::streamsize>(buff.size()));
if (_istream.bad())
throw std::exception("stream_read");
}
protected:
template <typename T> T get_basic_t()
{
if (_istream.eof())
throw std::exception("unexpected_eof");
T value;
_istream.read(reinterpret_cast<char *>(&value), sizeof(T));
if (_istream.bad())
throw std::exception("stream_read");
return value;
}
template<> bool get_basic_t()
{
long value = get_long();
if (_istream.bad())
throw std::exception("stream_read");
if (value == true_pattern)
return true;
else if (value == false_pattern)
return false;
else
throw std::exception("data_corrupt");
}
template<typename T> T get_string_t()
{
long len = get_long();
T value;
value.resize(len / sizeof(T::value_type));
if (_istream.eof())
throw std::exception("unexpected_eof");
_istream.read(reinterpret_cast<char*>(&value[0]), len);
if (_istream.bad())
throw std::exception("stream_read");
return value;
}
private:
std::istream& _istream;
deserializer& operator =(const deserializer&);
};
}

说明:

1. 使用独立的名字空间,避免名字冲突;

2. 对于bool型,理论上使用一个bit就可以表达其所有的信息。但是我们实际使用还是一个字节,因为这是内存操作的最小单位。为了有效地利用额外的信息,我们这里加入了对bool的校验,使用两个特殊的值;这两个值可以是任意值,只要不同即可。

3. 实现两个类,分别完成基本对象,字符串及其宽字符版本以及POD数据的序列化和反序列化;

4. 使用成员模板来消除基本对象序列化/反序列话过程中的代码重复;

5. 对于I/O操作失败,我们直接抛出异常。注意,这不是临时方案。而是经过仔细设计的序列化/反序列化过程的错误处理机制;

6. 构造这些对象需要输出或输入流对象,而不仅仅限于文件;

有了这个基础,我们就可以设计通用的序列化/反序列化接口了,非常简单:

[cpp] view plaincopy
namespace persistent {
class serializable
{
public:
virtual void serialize(serializer& out) const = 0;
virtual void deserialize(deserializer& in) = 0;
};
}

那么,这个接口怎么使用呢?假设你有如下类,如果实现对其序列化/反序列化的支持呢?

[cpp] view plaincopy
struct mydata
{
char _c;
wchar_t _w;
bool _b;
short _s;
long _l;
int64 _64;
double _d;
std::string _str;
std::wstring _wstr;
};

下面就是实现代码以及用来测试的代码:

[cpp] view plaincopy
struct mydata : public serializable
{
char _c;
wchar_t _w;
bool _b;
short _s;
long _l;
int64 _64;
double _d;
std::string _str;
std::wstring _wstr;
public:
virtual void serialize(serializer& out) const
{
out.put_char(_c);
out.put_wchar(_w);
out.put_bool(_b);
out.put_short(_s);
out.put_long(_l);
out.put_llong(_64);
out.put_double(_d);
out.put_string(_str);
out.put_wstring(_wstr);
}
virtual void deserialize(deserializer& in)
{
_c = in.get_char();
_w = in.get_wchar();
_b = in.get_bool();
_s = in.get_short();
_l = in.get_long();
_64 = in.get_llong();
_d = in.get_double();
_str = in.get_string();
_wstr = in.get_wstring();
}
bool operator == (const mydata& r)
{
return _c == r._c &&
_w == r._w &&
_b == r._b &&
_s == r._s &&
_l == r._l &&
_64 == r._64 &&
_d == r._d &&
_str == r._str &&
_wstr == r._wstr;
}
};
void test_serializer()
{
mydata data;
data._c = 'X';
data._w = L'q';
data._b = false;
data._s = 12;
data._l = 75543;
data._64 = 0xabcdef0123;
data._d = 12.47;
data._str = "hello";
data._wstr = L"world";
std::stringstream ssm;
serializer s(ssm);
data.serialize(s);
mydata data2;
deserializer ds(ssm);
data2.deserialize(ds);
assert(data == data2);
}

注意到了吗?实现一个对象的序列化/反序列化操作需要:

1. 从接口serializable中继承

2. 实现相应的接口。对于序列化,按照顺序依次把成员写入流;对于反序列化,则要严格按照相反的顺序依次读入

3. 如果某个成员不是基本数据类型,有两种选择:a. 实现该成员所属类型的序列化/反序列化,然后直接调用之;b. 直接序列化该成员的子成员。这样做会严重破坏对象的完整性,所以非常不推荐使用。

实现了对象的序列话之后,使用起来就特别容易了。在我们的测试代码中,我们把对象序列化为一个字串,然后反序列化之到另一个对象。我们最后得到两个状态完全一致的对象。如果我们把该字串通过socket传到远方,实际上就是对象的远程传递了。所有流行的远程对象调用都是用类似的基础机制达成其目的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: