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

  • inherits from CObject,

  • has DECLARE_SERIAL and IMPLEMENT_SERIAL, and

  • has its own Serialize defined

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); 
    } 
} 


    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference