又是倾分相送: 如果用标准C++实现对象的持久性.(季老大请进,优先给分)

freeia 2003-10-17 04:31:35
大家看清题意了吧.
请指点一下,谢谢了.

看了一下程序员2002第5期,不过是电子版的,不是很清楚,找不到例子原代码,还是没搞懂.
...全文
34 34 打赏 收藏 转发到动态 举报
写回复
用AI写文章
34 条回复
切换为时间正序
请发表友善的回复…
发表回复
Siney 2003-10-20
  • 打赏
  • 举报
回复
你到底什么问题?如果仅是源代码不能编译通过,CSDN不是有相关源代码下载吗?
http://www.csdn.net/magazine/download.shtm
cident 2003-10-20
  • 打赏
  • 举报
回复
顶!
freeia 2003-10-20
  • 打赏
  • 举报
回复
顶!
各位都没招?
liansdan 2003-10-19
  • 打赏
  • 举报
回复
占个位置搞学习
只想接那10分^_^
Hermit 2003-10-18
  • 打赏
  • 举报
回复
down
freeia 2003-10-18
  • 打赏
  • 举报
回复
To Aweay:
到底是怎么把非定长对象存储到文件中的?

建立一个对象数组? Object[MAX_NUM]?
一个一个的接着写进文件?

然后读取时,一个接一个的读取出来,在读取之前,检测一下类型,根据类型动态生成一个对象?

Is that all?
cident 2003-10-18
  • 打赏
  • 举报
回复
不同情况的结果如图二所示:(图形见程序员杂志2002.5期,不是我不想用键盘敲出来,而是我没那个本事
这里的关键在于:m_apObj在写入、读出时保存了完全相
同的、唯一的对象地址(最重要的是顺序相同),而索引号
恰好表示了对象在数组中的下标!下面是相关代码(注意对
空指针做了特殊处理,认为空指针已写入):
class CPstream {
...
public :
CPstream (const char *szName, const char *szMode) {
...
m_apObj[0] = NULL ;
m_iCur = 1 ;
}
...
private :
enum PointerTypes {ptIndexed, ptObject} ; // pointer tag
enum {MAXOBJECT = 1024} ;
const CShape *m_apObj[MAXOBJECT] ;
int m_iCur ; // current obj number
void AddObject (const CShape *pObj) {
m_apObj[m_iCur++] = pObj ;
}
int FindObject (const CShape *pObj) ;
CPstream& operator << (const CShape *pshp) ;
CPstream& operator >> (const CShape *&pshp) ;
} ;
int CPstream::FindObject (const CShape *pObj) {
for (int i = 0 ; i < m_iCur ; ++i)
if (m_apObj[i] == pObj) return i ; // find it, return index
return -1 ; // not find, return -1
}
有了以上改进,要解决上述三个问题就易如反掌了。注
意operator >>被声明为指针的引用,因为我们需要改变pshp
的值(指向新的对象)。
CPstream& CPstream::operator << (const CShape *pshp) {
int iIndex = FindObject (pshp) ;
if (iIndex != -1) // already write before!
return *this << (int)ptIndexed // write index tag
<< iIndex ; // write index
else {
AddObject (pshp) ; // record it!
return *this << (int)ptObject // write object tag
<< pshp->GetRegisterNo () // write register No.
<< *pshp ; // write object
}
}
CPstream& CPstream::operator >> (const CShape *&pshp) {
int iTag, iIndexOrNo ;
*this >> iTag >> iIndexOrNo ;
CShape *pshpTemp ;
switch (iTag) {
case ptIndexed :
pshp = m_apObj[iIndexOrNo] ;
break ;)
case ptObject :
pshpTemp = (s_apfnCreate[iIndexOrNo])() ; // create object!
AddObject (pshpTemp) ; // record it!
*this >> *pshpTemp ; // read object
pshp = pshpTemp ;
break ;
}
return *this ;
}
测试实例
下面是一次测试的交互输出(图三):
读、写完成后的内存布局(图四):
输出文件(test3)的内容(图五):
改 进
为了把关键问题讲清楚,我采用了尽可能简单的实现方
案。下面开始讨论其它一些可行的办法,有兴趣的读者可以在
原来的基础上修改:
1.数组m_apObj的大小是固定的,可以换用可动态生长
的数组或链表实现。
2.FindObject效率较低(O(n) 复杂度),可以用排序数
组或字典结构实现(注意由于读写顺序在上述结构中和位置
没有对应关系,所以顺序号必须保存起来!)。
3.数组s_apfnCreate的大小是固定的,也可以换用可动
态生长的数组或链表实现。
4.类型信息可以换成字符串以加强可读性。
5.对于数0x12345678(int),在little-endian(Intel)中
输出为78 56 34 12,在big-endian(Motorola)中输出为12
34 56 78,从而产生移植问题。一种方案把数据转为ANSI
或UNICODE字符集输出;第二种就是在CPstream中加入变量
static bool s_bLittle并调用操作系统函数决定其值,然后在
基本类型的插入、提取中根据s_bLittle做相应转换。
到此,我们已经看到了C++持久性的实现方法。读者可
以到《程序员》网站下载与本文相关的完整代码。

总算完了,好累啊!
各位继续讨论吧.我笨,不太懂其中的奥妙.
cident 2003-10-18
  • 打赏
  • 举报
回复
如果在C++中没有继承、没有指针,上面的实现确实简
洁而完美。但现实总是要复杂许多,下面是一个测试程序
(部分)。基本过程是:生成不同的CShape派生对象并保存
在一个CShape*的数组中(数组名为apshp),然后把这些对
象写入文件,最后再从中读回。
CShape * apshp[5];
//不同类型的CShape派生对象已经放进了apshp数组里面。
for (int j = 0 ; j < sizeof apshp / sizeof apshp[0] ; ++j) {
//这里调用的肯定是CPstream & operator << (CPstream & ps,const CShape * pshp),
//所以什么都不会输出,错误!
ps << apshp[j] ;
}
// 这里就把这些对象删除了
CPstream ps ("test3", "rb") ;
for (int i = 0 ; i < sizeof apshp / sizeof apshp[0] ; ++i) {
// 怎么创建新对象?
// apshp[i] = new C????
ps >> apshp[i] ; // hanging pointer!
}
大家可能会想:这么古怪的程序在实际项目中存在吗?
其实在容器类、视窗系统的实现中,上述类似代码会常常见
到,只不过调用者是客户代码而已!好了,言归正传,该谈
谈出现的问题了:
1.虽然在apshp数组可以保存指向CShape各种派生类对象
的指针,但是对这句代码ps << apshp[i]而言,它永远只会调用
CPstream& operator << (CPstream &ps, const CShape *pshp),
而不会根据指针指向的实际对象调用对应的重载版本。根本原
因在于operator <<不是虚函数!我们也不可能简单地在它前面
加上virtual就搞定一切,因为operator <<并不是一个成员函数!
嗯,怎么办?好办!我们只要在CShape中加上函数virtual void
WriteData (CPstream& ps),在operator <<中直接调用它就行了。
根据虚函数的特性,operator<<的调用会转发到对应类型的
WriteData上,现在我们的任务只是根据需要改写派生类的WriteData
函数即可,而且再也不需要为每个类重载一个operator <<了,真
是一举两得!同样的情况也出现在operator >>中。
class CShape {
friend class CPstream ;
protected :
virtual void WriteData (CPstream &) const {} // no data to write
virtual void ReadData (CPstream &) {} // no data to read
...
} ;
class CPstream {
...
public :
CPstream& operator << (const CShape &shp) {
shp.WriteData (*this) ; return *this ;
}
CPstream& operator >> (CShape &shp) {
shp.ReadData (*this) ; return *this ;
}
} ;
2.当从文件中读回对象时,我们必须先为apshp[i]生成一
个相应的对象!可是我们怎么知道该生成CShape还是CSquare
还是⋯⋯?好像真的无从下手,怎么办?一个可行的办法
是:在写入数据前先加入类型信息,读入时让CPstream根据它
为我们自动生成相应的对象。如何实现呢?下面是我所能想
到的最简单的办法:首先为每个类加上静态函数static CShape*
CreateObject (),其作用就是调用new生成一个对象;接着在
CPstream中加入指向CreateObject的静态指针数组static
PFNCREATE s_apfnCreate[MAXCLASS]和静态函数static int
RegisterClass (PFNCREATE pfnCreate) ,其作用就是把
pfnCreate保存在s_apfnCreate中,并返回pfnCreate在数组中的
索引; 最后就是在每个类中加入静态变量s t a t i c i n t
s_iRegisterNo(用来记录调用RegisterClass的返回值)和一个
虚函数virtual int GetRegisterNo () const(用来返回
s_iRegsisterNo),而这个就是我们的类型信息了,在读回对
象时我们可以根据它从s _ a p f n C r e a t e 中找到相应的
CreateObject,从而产生正确的对象。下面是具体代码:
class CPstream {
...
private :
enum {MAXCLASS = 128} ;
typedef CShape* (*PFNCREATE) () ;
static PFNCREATE s_apfnCreate[MAXCLASS] ;
static int s_iRegNum ;
public :
static int RegisterClass (PFNCREATE pfnCreate) {
s_apfnCreate[s_iRegNum] = pfnCreate ;
return s_iRegNum++ ;
}
...
} ;
CPstream::PFNCREATE CPstream::s_apfnCreate[] ;
int CPstream::s_iRegNum ;
class CShape {
...
private :
static int s_iRegisterNo ;
static CShape* CreateObject () ;
virtual int GetRegisterNo () const ;
} ;
int CShape::s_iRegisterNo = CPstream::RegisterClass (CShape::CreateObject) ;
CShape* CShape::CreateObject () {
return new CShape ;
}
int CShape::GetRegisterNo () const {
return s_iRegisterNo ;
}
3.这个问题比较隐蔽,在代码中没有直接表现出来。
想想看,如果有多个指针指向同一个对象会产生什么结果?
这个对象会被重复写入文件多次,在读出时会生成许多不同
的对象,而这些指针会分别指向它们。尽管每个指针所指向
的对象内容都一样,但还是错的,因为两次的内存布局完全
不同了。如何解决? 下面是一种比较简单的方法: 在
CPstream中加入数组const CShape *m_apObj[MAXOBJECT]
和两个函数void AddObject (const CShape *pObj) 、int
FindObject (const CShape *pObj),一个负责把pObj插入数
组尾端,一个在数组中查找pObj并返回其索引(没有找到就
返回-1);在写入指针时先调用FindObject,如果返回-1
(第一次写入该对象)就调用AddObject将其记录在案,并写
入一个标志(表示下面是对象),再按常规方法写入,否则
就写入一个不同的标志(表示下面是索引),再把返回的索
引号写入;在读出指针时先读出标志,如果下面是对象就常
规方法读出并调用AddObject记录新生成的对象地址,否则就
把索引号读出并根据它从m_apObj返回已有的对象。这两种
cident 2003-10-18
  • 打赏
  • 举报
回复
今天就辛苦一下,我来贴出文章,大家一起来研究:)
(各位注意,由于有密码,我用键盘一个一个敲出来的啊)

如何实现C++对象的持久性?
撰文/ 肖翔
本文介绍了C++对象持久性的简单实现和一些常见的问题。
关键词:C++ 持久性 I/O流
前 言
1998年ANSI/ISO C++标准委员会制定了第一个C++标
准, 大大加强了t e m p l a t e 的语法, 并引入了命名空间
(namespace)、异常(exception)和动态类型识别(RTTI)
等新的语言特性,使得C++成为一种极其强大的程序设计语
言。但是该标准并没有为对象持久性作出任何规定,从而使
得许多人都不太熟悉这个非常重要的问题。本文的目的就是
给出一个非常小巧的实现方案,并对涉及到的技术问题和其
他的实现方法进行一些讨论。
首先我要解释一下持久性(persistence)这个概念。其实
很简单,就是把在内存中的对象保存到文件里,使得程序重
新加载后能够完全恢复到退出前的状态,这也正是持久性的
意义所在!粗看起来这似乎和iostream差不多,都涉及到文件
操作和数据读写,似乎没有什么新东西。呵呵,要是这样的
话大家就不会看到这篇文章了。下面就是一个通常大家都会
想到的实现,不幸的是其中包含了不少错误,我会在后面一
一举出,并给出解决办法。
简单版本(有错误)
由于篇幅限制,下面的代码略去了错误检查和雷同部分。
整个想法很简单:先用标准C库函数fopen、fclose、fwrite、fread实
现底层的文件操作,再对所有基本类型重载插入和提取运算符。
class CPstream {
private :
FILE *m_pFile ;
void WriteBytes (const void *pData, size_t sz) {
::fwrite (pData, 1, sz, m_pFile) ;
}
void ReadBytes (void *pData, size_t sz) {
::fread (pData, 1, sz, m_pFile) ;
}
public :
// szMode: "wb" ==> open for write "rb" ==> open for read
CPstream (const char *szName, const char *szMode) {
m_pFile = ::fopen (szName, szMode) ;
}
~CPstream () {
::fclose (m_pFile) ;
}
CPstream& operator << (char ch) {
WriteBytes (&ch, sizeof (ch)) ; return *this ;
}
2002.05
PROGRAMMER
www.cs dn.net/magazine 85
CPstream& operator >> (char &ch) {
ReadBytes (&ch, sizeof (ch)) ; return *this ;
}
... // other fundamental types
} ;
下面是用于测试的类体系(图一)和对应的插入、提取
运算符:
图形见程序员杂志2002.5期
inline CPstream& operator << (CPstream &ps, const CShape &shp) {
return ps ; // not data output to file!
}
inline CPstream& operator << (CPstream &ps, const CShape *pshp) {
return ps << *pshp ; // forward reference version!
}
inline CPstream& operator << (CPstream &ps, const CSquare &sqr) {
return ps << (CShape &)sqr // first output base class data!
<< sqr.m_iX << sqr.m_iY << sqr.m_iHeight ; // second output self data!
}
inline CPstream& operator >> (CPstream &ps, CShape &shp) {
return ps ; // not data input from file!
}
inline CPstream& operator >> (CPstream &ps, CShape *pshp) {
return ps >> *pshp ;
}
inline CPstream& operator >> (CPstream &ps, CSquare &sqr) {
return ps >> (CShape &)sqr // first input base class data!
>> sqr.m_iX >> sqr.m_iY >> sqr.m_iHeight ; // second input self data!
}
...// other user types
虽然上面的代码有些问题(下一节会谈到),但还是有
虽然上面的代码有些问题(下一节会谈到),但还是有
很多地方是可取的:
1.指针版本调用引用版本,避免编写重复代码。
2.派生类版本调用基类版本,形成对应的层次结构。
3.被包容的对象直接调用插入、提取运算符。
问题及解决办法
Siney 2003-10-18
  • 打赏
  • 举报
回复
那么你哪里不清楚呢?
freeia 2003-10-18
  • 打赏
  • 举报
回复
CPStream(const char *szName, const char *szMode)
{
m_pFile = ::open(szName, szMode);
}

CPStream::~CPStream()
{
fclose(m_pFile);
}

void CPStream::WriteBytes(const void *pData, size_t sz)
{
::fwrite(pData, 1, sz, m_pFile);
}

void CPStream::ReadBytes(const void *pData, size_t sz)
{
::fread(pData, 1, sz, m_pFile);
}

CPStream& CPStream::operator << (char ch)
{
WriteBytes(&ch, sizeof(ch));
return *this;
}

CPStream& CPStream::operator >> (char ch)
{
ReadBytes(&ch, sizeof(ch));
return *this;
}
freeia 2003-10-18
  • 打赏
  • 举报
回复
你的意思跟他差不多,不过好像还不是很清楚明了.
我贴出一点程序员杂志上的代码:(不是我不贴全,而是上面的代码根本就不全,我觉得这个技术太实用了)
class CPStream
{
public:
CPStream(const char *szName, const char *szMode);
virtual ~CPStream();

private:
FILE *m_pFile;
virtual void WriteBytes(const void *pData, size_t sz);
virtual void ReadBytes(const void *pData, size_t sz);

CPStream& operator << (char ch);
CPStream& operator >> (char ch);


};
Siney 2003-10-18
  • 打赏
  • 举报
回复
对了,还应该有个RegisterClass函数,该函数把那个CreateObject放入那个数组,并返回其在数组中的位置,也就是作为对象的类型信息,保存进入文件时,加入这个类型信息,下次就知道如何动态创建了,呵呵。
Siney 2003-10-18
  • 打赏
  • 举报
回复
对象的持久性和rtti有很大关系,简单的来说就是对象被保存在文件后能不能再次生成相同的对象,不幸的是,标准c++对这方面支持的并不好,mfc和vcl都不同程度的扩展了语言特性以适应persist,这里的问题关键是能否正确的产生文件中保存的对象,因为cpp没有这样的语法:
Obj *aobj=new "TButton"();

所以使得这个问题变得复杂,那篇文章我好像也有影响,他的解决方法是:
先预先定制一个足够大的数组用于保存所有可能动态创建的对象;
而数组的类型返回创建类型(基类)的函数的指针:例如
typedef CBase* (*RegFunc)();

RegFunc m_AllObj[MAX_CLASS];

而每一个类都要提供一个动态创建的函数,CreateObject,上面的数组保存这个函数指针,这样需要动态创建的时候就要调用这个函数,问题就解决了。

--------------
不知道我的表述你是否明白,50分能给我吗:)?
appletreestudio 2003-10-18
  • 打赏
  • 举报
回复
Up
cident 2003-10-18
  • 打赏
  • 举报
回复
2002年5月份的程序员杂志里面有得介绍,但是我看不懂,源代码不全.:(
cident 2003-10-18
  • 打赏
  • 举报
回复
连楼上的牛人都不会,555~~~~~~~~~~~~~~~
yhz 2003-10-18
  • 打赏
  • 举报
回复
没办法,不会,只能 up 。
cident 2003-10-18
  • 打赏
  • 举报
回复
为啥C#和Java就很容易实现呢?
cident 2003-10-18
  • 打赏
  • 举报
回复
Up
加载更多回复(14)

13,822

社区成员

发帖
与我相关
我的任务
社区描述
C++ Builder相关内容讨论区
社区管理员
  • 基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧