概述
作者:Daniel Randle
本文节选翻译自《Pupping - a method for serializing data》
本期内容文字量爆炸,请慢慢阅读~
Pupping:一种序列化数据的方法(上)
声明
本文介绍的这种序列化方法并非作者原创,它属于阿拉斯加大学费尔班克斯分校的模拟课程教授Orion Lawlor。作者在听课之后,采用了教授的方法进行了创作。
介绍
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。有很多方法可以做到这一点,本文将讨论一种叫pupping的模式或方法,它与boost序列化库使用的方法非常相似。
包装和拆包
Pup代表打包解包(pack-unpack),因此puping是打包、解包(packing/unpacking)的意思。这个想法是这样的:与其为每种对象类型或每种类型的媒体(文件、网络、图形用户界面中的小部件等)创建序列化/反序列化函数,不如为每种类型的媒体创建pupper对象。pupper对象包含处理对特定介质的读/写所需的数据和函数。例如,二进制文件pupper可能包含每个pup函数用于从/到读/写的文件流。
说明
Puping这个模式与boost序列化非常相似,理解并学会运用这种模式就不需要再增加对boost的依赖性。“Pupper”在某种程度上等同于boost存档,而pup函数等同于boost序列化函数。
这里给出的代码比boost更简单,并且不会使运算符超载。它是非侵入性的,而且模型简单。即任何对象,无论多么复杂,都可以通过递归分解对象直到达到基本数据类型来序列化为字节流。任何基本数据类型都可以直接用字节表示。保存/加载/传输原始数据的过程可与序列化对象分开。然后,只需要编写代码来序列化对象,任何事情都可以用原始数据完成。
Pupper模式在几个方面与其他序列化方法不同:
1)除了在最低级别(pupper对象)中的读写操作之外不分开
2)需要序列化的对象不需要从任何特殊类继承(inherit)
3)开销小,不使用外部库,同时保持可扩展性和灵活性
4)如果需要多态序列化,则基类中需要一个虚方法。CRTP可用于辅助此过程。
不是在每个对象中创建一个类方法来提供序列化,而是为每个对象创建一个全局函数。除了应该序列化的对象的参数之外,所有全局函数都应具有相同的名称和参数。任何对象“可序列化”只是编写全局函数的问题。
stl容器(stl containers)的pop原型的一些示例如下所示。
template
pup(pupper * p, std::map & map, const var_info & info);
template
pup(pupper * p, std::vector & vec, const var_info & info);
template
pup(pupper * p, std::set & set, const var_info & info);
稍后将解释pupper指针和var_info的参数。重要的是序列化工作是在全局函数中完成的,而不是在成员函数中完成的。通过示例最简单地示出了PUP模式pup pattern。
在本文中,对二进制文件和文本文件保存/加载的pupper对象进行编码,并使用它们保存/加载一些示例对象。给出了使用pupper模式和CRTP来序列化多态对象的示例。此外,保存/加载了多态基础对象的std :: vector,说明了此模式在使用其他库定义类型(std :: vector)时允许的灵活性。所以不用多说,看看pupper头文件。
本文对二进制和文本文件保存/加载的pupper对象进行了编码,并使用它们保存/加载了一些例子。这里给出了一个使用pupper模式和CRTP序列化多态对象的示例。另外,一个多态基对象的std::vector 被保存/加载,以此说明这种模式使用其他库定义的类型(std::vector)的时候的灵活性。因此,不需要更加麻烦的操作,现在就来看看pupper文件的开头header file
#define PUP_OUT 1
#define PUP_IN 2
#include
#include
#include
struct var_info
{
var_info(const std::string & name_):
name(name_)
{}
virtual ~var_info() {}
std::string name;
};
struct pupper
{
pupper(int32_t io_):
io(io_)
{}
virtual ~pupper() {}
virtual void pup(char & val_, const var_info & info_) = 0;
virtual void pup(wchar_t & val_, const var_info & info_) = 0;
virtual void pup(int8_t & val_, const var_info & info_) = 0;
virtual void pup(int16_t & val_, const var_info & info_) = 0;
virtual void pup(int32_t & val_, const var_info & info_) = 0;
virtual void pup(int64_t & val_, const var_info & info_) = 0;
virtual void pup(uint8_t & val_, const var_info & info_) = 0;
virtual void pup(uint16_t & val_, const var_info & info_) = 0;
virtual void pup(uint32_t & val_, const var_info & info_) = 0;
virtual void pup(uint64_t & val_, const var_info & info_) = 0;
virtual void pup(float & val_, const var_info & info_) = 0;
virtual void pup(double & val_, const var_info & info_) = 0;
virtual void pup(long double & val_, const var_info & info_) = 0;
virtual void pup(bool & val_, const var_info & info_) = 0;
int32_t io;
};
void pup(pupper * p, char & val_, const var_info & info_);
void pup(pupper * p, wchar_t & val_, const var_info & info_);
void pup(pupper * p, int8_t & val_, const var_info & info_);
void pup(pupper * p, int16_t & val_, const var_info & info_);
void pup(pupper * p, int32_t & val_, const var_info & info_);
void pup(pupper * p, int64_t & val_, const var_info & info_);
void pup(pupper * p, uint8_t & val_, const var_info & info_);
void pup(pupper * p, uint16_t & val_, const var_info & info_);
void pup(pupper * p, uint32_t & val_, const var_info & info_);
void pup(pupper * p, uint64_t & val_, const var_info & info_);
void pup(pupper * p, float & val_, const var_info & info_);
void pup(pupper * p, double & val_, const var_info & info_);
void pup(pupper * p, long double & val_, const var_info & info_);
void pup(pupper * p, bool & val_, const var_info & info_);
~向下滑动~
首先声明一个var-info结构,它现在只具有一个名称字段(name field)——这是关于pupped变量的信息所属的地方。它在pupping过程中填写,因此会创建一个需要字段信息的构造函数,这样以后就不会忘记它了。pupper基类定义了任何类型的pupper必须执行的一组方法,一种处理从/到媒体的每个基本数据类型的读/写的方法。一组名为“pup”的全局函数被定义并且被宣布了,同时还建立了pupping模式的基本用法。
其思想是能够在代码的任何地方调用pup(pupper、object、description),以便序列化/反序列化任何可序列化的对象。创建新的pupper对象类型包括对每个基本数据类型执行pup方法。这些方法随后由pup全局函数使用,而pup函数又用于更复杂的类型。无论创建了多少新的pupper类型,序列化每个对象的pup函数只需要编写一次。这正是使这种模式有用的原因。
要使所有对象都可序列化为二进制文件/文本文件/Qt对话框(Qt dialog),首先要创建二进制文件/文本文件/Qt对话框 pupper。某些类型的pupper对象可能需要有关变量的其他信息。例如,有多种方法可以在图形用户界面中表示双精度数,例如垂直滑块、水平滑块、数字显示框等。
var_info结构允许添加有关变量的新信息。任何不需要该信息的pupper对象都可以忽略它。使用Qt作为例子,可以将标志(flag)添加到var_info结构并由Qt pupper对象使用。然后,需要在图形用户界面中显示的对象需要设置标志,所有不使用该标志的pupper对象都会忽略它。通过将var_info的析构函数变为虚函数,可以扩展var_info结构。如果创建其他人将使用的库,这也很有用。它允许用户创建自己的pupper对象类型,并将任何必要的数据添加到var_info,而无需编辑库源代码。
使用pup(pupper,object,description)而不是pupper-> pup(object,description)或object-> pup(pupper,description)有几个原因。
不使用pupper-> pup(对象,描述)的原因:
1)必须为每种新类型的对象扩展基础pupper种类。如果使用可扩展类创建库,则库的用户必须为扩展的每个类编辑基本pupper种类,其中库仍负责序列化。
2)打包/解包代码将与对象分离,使其在对对象进行更改时容易出错。
不使用object-> pup(pupper,description)的原因:
1)不能轻易地扩展第三方库对象(如std::vector)来包含pup函数,它们需要一个特殊的函数或包装类(wrapper class)
2)因为许多对象不包含“pup”函数,所以会与pup的用法不一致。
虽然这纯粹是一个关于美学和便利的争论,当然这也是一种观点。但我认为应该这样写:
pup(pupper,obj1,desc1);
pup(pupper,obj2,desc2);
pup(pupper,obj3,desc3);
pup(pupper,obj4,desc4);
//etc...
是不是比下面这个更好理解和记忆。
obj1->pup(pupper,desc1);
pup(pupper,obj2,desc2);
obj3->pup(pupper,desc3);
pup(pupper,obj4,desc4);
//etc...
如果所有内容都使用相同的pup函数格式,那么编写pup函数就变得很简单,因为它们只是相同格式的不同pup函数的组合。创建具体的pupper对象很容易——以二进制和文本文件pupper对象作为例子。它们的定义代码很无聊,所以这里不会为大家展示,但是其声明(declarations)如下。
//binary_file_pupper header
#include "pupper.h"
struct binary_file_pupper : public pupper
{
binary_file_pupper(std::fstream & fstrm, int mode);
std::fstream & fs;
void pup(char & val_, const var_info & info_);
void pup(wchar_t & val_, const var_info & info_);
void pup(int8_t & val_, const var_info & info_);
void pup(int16_t & val_, const var_info & info_);
void pup(int32_t & val_, const var_info & info_);
void pup(int64_t & val_, const var_info & info_);
void pup(uint8_t & val_, const var_info & info_);
void pup(uint16_t & val_, const var_info & info_);
void pup(uint32_t & val_, const var_info & info_);
void pup(uint64_t & val_, const var_info & info_);
void pup(float & val_, const var_info & info_);
void pup(double & val_, const var_info & info_);
void pup(long double & val_, const var_info & info_);
void pup(bool & val_, const var_info & info_);
};
template
void pup_bytes(binary_file_pupper * p, T & val_)
{
if (p->io == PUP_IN)
p->fs.read((char*)&val_, sizeof(T));
else
p->fs.write((char*)&val_, sizeof(T));
}
//text_file_pupper header
#include "pupper.h"
struct text_file_pupper : public pupper
{
text_file_pupper(std::fstream & fstrm, int mode);
std::fstream & fs;
void pup(char & val_, const var_info & info_);
void pup(wchar_t & val_, const var_info & info_);
void pup(int8_t & val_, const var_info & info_);
void pup(int16_t & val_, const var_info & info_);
void pup(int32_t & val_, const var_info & info_);
void pup(int64_t & val_, const var_info & info_);
void pup(uint8_t & val_, const var_info & info_);
void pup(uint16_t & val_, const var_info & info_);
void pup(uint32_t & val_, const var_info & info_);
void pup(uint64_t & val_, const var_info & info_);
void pup(float & val_, const var_info & info_);
void pup(double & val_, const var_info & info_);
void pup(long double & val_, const var_info & info_);
void pup(bool & val_, const var_info & info_);
};
template
void pup_text(text_file_pupper * p, T val, const var_info & info, std::string & line)
{
std::string begtag, endtag;
begtag = " + info.name + ">"; endtag = "";
if (p->io == PUP_OUT)
{
p->fs << begtag << val << endtag << "n";
}
else
{
std::getline(p->fs, line);
size_t beg = begtag.size(); size_t loc = line.find(endtag);
line = line.substr(beg, loc - beg);
}
}
~向下滑动~
未完待续声明:本公众号文章,禁止转载。违者必究!
如果你有一肚子干货,还有满满的表达欲望
请火速联系小编,小编将水陆空全力支持你!
联系邮箱:menglong@kingsoft.com
长按指纹
一键关注
最后
以上就是专注百褶裙为你收集整理的qt 保存富文本到数据_干货分享 | Pupping:一种序列化数据的方法(上)的全部内容,希望文章能够帮你解决qt 保存富文本到数据_干货分享 | Pupping:一种序列化数据的方法(上)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复