29.5 Serializing pointers
Serializing pointer members
CPopDoc
has a cGame* _pgame object as its most important member. Serializing the cGame *_pgame pointer takes a bit of care.
Something to realize is that when you load into a CPopDoc, that CPopDoc
object will already exist, so it will have been initialized by a constructor call. So the _pgame will in fact be a valid pointer. Whenever you load into a valid pointer variable ptr, you have to call delete
on the pointer first, otherwise you'll have a memory leak caused by the 'orphaned' object that the pointer pointed to before you overwrote it with the load. For reasons we'll now explain, we must use an overloaded ar >> ptr operator to load into a pointer, rather than a call like ptr->Serialize(ar).
To save and load the _pgame fields of CPopDoc, we use the autogenerated overloaded operator<<(CArchive &ar, cgame *p)
and operator>>(CArchive &ar, cGame
*&p). MFC has 'written the code' for these operators automatically because the cGame
In the load case we want to make a new cGame
* and place it into the _pgame field, and this is exactly what ar >> _pgame does.
Now, as mentioned just above, in the load case, we delete _pgame before loading it. At first you might think you could load either with _pgame->Serialize(ar) or ar >> _pgame. But since you delete _pgame just before the load, it becomes an invalid pointer just before the load, and you would get a crash if you tried to call _pgame->Serialize(ar) for the load. We could actually use _pgame->Serialize(ar) in the save case, but for symmetry in the appearance of the read and write cases, we use ar << _pgame there.
Here's a partial listing of the cPopDoc::Serialize.
void CPopDoc::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsStoring()) // Save
ar << _pgame;
else //Load
{
delete _pgame; /*At CPopDoc construction a document creates a
default cGame *_pgame. So if we're loading a game we need
to delete the existing game first or there will be a
memory leak.*/
ar >> _pgame; /* Uses CreateObject to creates a new cGame*
object of the correct child class, copies the new objects
fields out of the file, and places the pointer to the new
object in _pgame. */
_pgame->setGameover(TRUE); /* So you can press ENTER to
actually start it running. _brandnewgameflag will have
been set to TRUE by the constructor call inside the ar >>
call, so the first ENTER won't randomize things. */
UpdateAllViews(NULL, CPopDoc::VIEWHINT_STARTGAME, 0);
}
}
Serializing reference pointers
One exception to the principle of 'serialize everything in sight' is when your objects have pointer members that are used as references to point to other objects that may or may not be getting serialized as well. This is, in other words, a case where our code actually has two or more copies of the same pointer in two different locations. One of these copies is the 'member' and this copy gets serialized as just described. But the other copies are meant only to echo the address value of the member pointer object. In these cases we need to do something a little tricky.
The cGame
class, for instance, has a separate cCritter* _pplayer pointer that is the same value as one of the cCritter
* actually in the cBiota *_pbiota
member. We track the index of where it appears in the cBiota
array, if it does appear, and we save that. Here's some of the relevant code.
void cGame::Serialize(CArchive& ar)
{
int playerindex;
CObject::Serialize(ar);
/*It's worth noting that when we call this next line in
loading mode, the _pbiota will be pointing in a non-NULL
cBiota that was created by the cGame constructor, so we'll
need to have the cBiota::Serialize take care of deleting
members of an existing cBiota before loading into it. */
_pbiota->Serialize(ar);
if (ar.IsStoring()) // Save
{
playerindex = _index(_pplayer);
ar << _border << /* ETCETERA */ << playerindex;
}
else //Load
{
ar >> _border >> /* ETCETERA */ >> playerindex;
/* _pplayer currently equals NULL or one of the old
dummy pointers in the cBiota, either way we don't have
to delete it. Remember it's only a reference
copy. */
_pplayer = _pbiota->GetAt(playerindex);
}
}
|