Introduction
This is an article written by a beginner for beginners. Or at least, by a beginner for his own benefit, but he is posting it up on CodeProject because he is hoping that it will be of interest to others too. The style is intended to be useful to someone who is still relatively new (but not completely new) to setting up projects using the Visual Studio 6 IDE. The content of the article may be of interest to someone who is familiar with the technique of serializing an array of simple objects contained in a CObArray
container, but who has wondered how this method can be extended to more complex data structures.
Background
I wrote this program to find a way of storing and recalling information about relationships between objects using MFC serialization techniques. From the names of my custom classes I have used (CNode
and CSubNode
), it may be apparent to the reader that I am interested in investigating ways of modeling networks. The project discussed in this article represents a possible method for storing and recalling various network structures.
Using the code
The zip file that is available with this article contains the entire code for the project described below. The code pertinent to the serialization of a collection of collections is in the Serialize
functions of the CSerializeExpDoc
, CNode
, and CSubNode
classes.
In the example project, I have made the first level of objects being collected of type 'CNode
' and the objects those objects collect of type 'CSubNode
'.
The CNode
class inherits from CSubNode
which, in turn, inherits from CObject
. CNode
, therefore, inherits the CObject
properties via CSubNode
as well as a few other members.
For the serialization to work, the CNode
class doesn't have to be derived from CSubNode
, but if it wasn't, CNode
would explicitly have to inherit from CObject
using the following declaration in the file 'Node.h':
class CNode : public CObject
instead of:
class CNode : public CSubNode
Also, the GetString
, SetString
, and m_strString
members would have to be replicated in CNode
.
The code for this project has been made available for you to download, peruse, compile, and run as you like.
Here follows a description of creating the project from beginning to end...
Create the skeleton application
Create a Single Document Interface program with Document/View architecture support. The project here is called 'SerializeExp'. De-select ActiveX. You don't specifically need ActiveX here. On stage four of the Application Wizard, click 'Advanced' and choose the file extension to be used by your application's data files. On step 6, change the base class to 'CFormView
'. You can now click 'Finish' to build your skeleton project.
Make up the form and prepare the control variables
The necessary controls to be placed on the form can be divided into two groups: those associated with nodes and those associated with sub nodes.
Here are the controls with the View class member variables to be associated with them via the Class Wizard:
Control Type | Control ID | View Class Member Variable | Other Information |
---|
group box | IDC_STATIC | - | Caption: Node |
static text | IDC_SNPOSITION | CString m_sNPosition | Caption: Node information\n\nRecord 0 of 0 |
group box | IDC_STATIC | - | - |
edit box | IDC_ENOD | CString m_sNod | - |
button | IDC_BNODLEFT | - | Caption: < |
button | IDC_BNODRIGHT | - | Caption: > |
group box | IDC_STATIC | - | Caption: Sub Node |
static text | IDC_SSPOSITION | CString m_sSPosition | Caption: Sub Node information\n\nRecord 0 of 0 |
group box | IDC_STATIC | - | - |
edit box | IDC_ESUBNOD | CString m_sSubNod | - |
static text | IDC_STATIC | - | - |
edit box | IDC_ESNWEIGHT | short m_shortSubNodeWeight | Styles: Number |
button | IDC_BSUBNODLEFT | - | Caption: < |
button | IDC_BSUBNODRIGHT | - | Caption: > |
Create the custom classes
Right-click over where it says 'SerializeExp' in the ClassView workspace, and click on 'New Class...'. Change the class type to 'Generic Class', call it 'CSubNode
', and derive it from 'CObject
' as public
.
Repeat the above instructions to create another class called 'CNode
', but this time, derive it from 'CSubNode
' as public
instead of 'CObject
'.
Add the class variables and methods for reading and writing variables
To the class CNode
, add a public
variable called m_oaSubNodes
of type CObArray
, and a private
variable called m_iSubNodePosition
of type int
.
To the class CSubNode
, add a private
variable called m_shortWeight
of type short
, and a protected
variable called m_strString
of type CString
.
One of the sources I based this project on (Chapman) uses inline functions for getting and setting class variables. Inline functions are faster to execute than non-inline functions, but can make executable files physically larger, since, when the program is compiled, the entire function is copied to each point in the program where it is called, instead of causing a jump in the execution of the program to a single instance of the code.
An inline function is created simply by putting the implementation of the function immediately after the declaration in the class' header file. So for the subnode class, you should add inline functions for getting and setting m_strString
and m_shortWeight
, by putting the following lines with the 'public
' declarations in the header file.
void SetString(CString inPath) {m_strString = inPath;}
CString GetString() {return m_strString;}
void SetWeight(short inWeight) {m_shortWeight = inWeight;}
short GetWeight() {return m_shortWeight;}
Implement constructors
Here, the initial values are set for the members of the new custom class object members.
For CSubNode
, implement the constructor as follows:
CSubNode::CSubNode()
{
m_shortWeight=1;
m_strString="";
}
and for CNode
...
CNode::CNode()
{
m_strString = "";
m_iSubNodePosition = 0;
CSubNode *pSubNode = new CSubNode();
try
{
m_iSubNodePosition = (m_oaSubNodes.GetSize() - 1);
}
catch (CMemoryException* perr)
{}
}
Add the member variables for the document class
Using the ClassView, add the following private
variables of type 'int
'...
m_iCurPosition
m_iSubNodePosition
Then, add a public
variable of type 'CObArray
', called m_oaNodes
.
Make the custom classes serializable
Making the custom classes serializable involves the following steps:
- Add '
DECLARE_SERIAL
' to the declaration file macros. - Add '
IMPLEMENT_SERIAL
' to the implementation file macros. - Add '
Serialize
' functions to the classes to deal with the CArchive
object, which is where the C++ save and restore streams are handled.
- The
DECLARE_SERIAL
macro is placed at the beginning of the class declarations of the classes you want to serialize. Use the name of the class as the argument for the macro. - The
IMPLEMENT_SERIAL
macro goes at the end of the macros at the top of the implementation files for each class you want to make serializable. This macro takes three arguments, namely, the name of the class, its base class, and a version number which should be changed if you change the data structures that you serialize when you update your application. The contents of SubNode.h should now look something like this...
#if !defined(AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2_9996E51F4457
__INCLUDED_)
#define AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2_9996E51F4457
__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CSubNode : public CObject
{
DECLARE_SERIAL (CSubNode)
public:
CSubNode();
virtual ~CSubNode();
private:
short m_shortWeight;
protected:
CString m_strString;
};
#endif // !defined(AFX_SUBNODE_H__3C84564E_BBEE_4B26_99B2
_9996E51F4457__INCLUDED_)
...and the contents of Node.h should look like this...
#if !defined(AFX_NODE_H__A68ACB18_C472_487F_BACF_BADC4304203D
__INCLUDED_)
#define AFX_NODE_H__A68ACB18_C472_487F_BACF_BADC4304203D
__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "SubNode.h"
class CNode : public CSubNode
{
DECLARE_SERIAL (CNode)
public:
CObArray m_oaSubNodes;
CNode();
virtual ~CNode();
private:
int m_iSubNodePosition;
};
#endif // !defined(AFX_NODE_H__A68ACB18_C472_487F_BACF
_BADC4304203D__INCLUDED_)
The contents of the CSubNode
implementation file should look like this...
#include "stdafx.h"
#include "SerializeExp.h"
#include "SubNode.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
IMPLEMENT_SERIAL (CSubNode, CObject, 1)
CSubNode::CSubNode()
{
m_shortWeight=1;
m_strString="";
}
CSubNode::~CSubNode()
{
}
... and the contents of the CNode
implementation file should look like this...
#include "stdafx.h"
#include "SerializeExp.h"
#include "Node.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
IMPLEMENT_SERIAL (CNode, CSubNode, 1)
CNode::CNode()
{
m_iSubNodePosition = 0;
CSubNode *pSubNode = new CSubNode();
try
{
m_iSubNodePosition = (m_oaSubNodes.GetSize() - 1);
}
catch (CMemoryException* perr)
{}
}
CNode::~CNode()
{
}
- Now it is time to create the functionality for saving and restoring data for the custom classes via '
Serialize
' functions. Using the ClassView tab in the workspace, add public
functions of type 'void
' to both CSubNode
and CNode
, both with the declaration 'Serialize(CArchive &ar)
'.
In this application, the part of the serialization process we are concerned with starts in the document object. The document class object's Serialize
function passes a reference of the archive object to its CObArray
collection of CNode
objects via CObArray
's own serialization function. This causes each CNode
object to receive a reference to the archive object in turn, repeating the process internally - passing a reference of the archive object to its collection of CSubNode
objects, causing them to become serialized in turn.
Then, for the CSubNode
function, it is just a matter of streaming the object's data to or from the archive object, depending on whether or not the data is being saved or retrieved...
To kick off, edit the document class function so that an archive object reference gets passed to its collection of CNode
objects...
void CSerializeExpDoc::Serialize(CArchive& ar)
{
m_oaNodes.Serialize(ar);
}
Now, edit the CNode
class Serialize
function. This function serializes its own non-collection data before calling the Serialize
function of its CSubNode
object collection.
The base class for each serializable object must be given the opportunity to serialize its data before the derived class object serializes. Therefore, every 'Serialize
' function must start with a call to the Serialize
function of the base class. This is 'CObject
' for 'CSubNode
' objects and 'CSubNode
' for 'CNode
' objects.
void CNode::Serialize(CArchive &ar)
{
CSubNode::Serialize(ar);
int iNoSubNodes = 0;
if (ar.IsStoring())
{
iNoSubNodes = m_oaSubNodes.GetSize();
ar << m_iSubNodePosition << m_strString << iNoSubNodes;
}
else
ar >> m_iSubNodePosition >> m_strString >> iNoSubNodes;
m_oaSubNodes.Serialize(ar);
}
At the end of the serialization chain, each CSubNode
object performs its own serialization...
void CSubNode::Serialize(CArchive &ar)
{
CObject::Serialize(ar);
if (ar.IsStoring())
ar << m_shortWeight << m_strString;
else
ar >> m_shortWeight >> m_strString;
}
Add the document class navigation and record handler functions
When the application is running, the Document Class navigates through the data as per instructions from the view class, and is responsible for creating new records and deleting old ones.
The code which goes into the project next refers to the CNode
and CSubNode
classes from within the Document class. In order to get the application to compile, you need to put in 'forward declarations' for the custom classes in the document class header file. This consists of placing the following lines...
class CNode;
class CSubNode;
... under the macros in the header file. The top of the header file for the document class should now look something like this...
#if !defined(AFX_SERIALIZEEXPDOC_H__90309A3B_DF70_4EFE_A06A_D15E6DC1FB45
__INCLUDED_)
#define AFX_SERIALIZEEXPDOC_H__90309A3B_DF70_4EFE_A06A_D15E6DC1FB45
__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CNode;
class CSubNode;
class CSerializeExpDoc : public CDocument
{
You will also need to put the following #include
lines in the document class implementation file...
#include "Node.h"
#include "SubNode.h"
...so that the top of the implementation file looks something like this...
#include "stdafx.h"
#include "SerializeExp.h"
#include "Node.h"
#include "SubNode.h"
#include "SerializeExpDoc.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
Now, create the following private
functions in the document class...
AddNewRecord()
of type CNode *
AddNewSubNode()
of type CSubNode *
These can then be implemented as follows...
CNode * CSerializeExpDoc::AddNewRecord()
{
CNode *pNode = new CNode();
CSubNode *pSubNode = new CSubNode();
try
{
pNode->m_oaSubNodes.Add(pSubNode);
m_oaNodes.Add(pNode);
SetModifiedFlag();
m_iCurPosition = (m_oaNodes.GetSize()-1);
}
catch (CMemoryException* perr)
{
AfxMessageBox("Out of Memory",MB_ICONSTOP | MB_OK);
if (pNode)
{
delete pNode;
pNode = NULL;
}
if (pSubNode)
{
delete pSubNode;
pSubNode = NULL;
}
perr->Delete ();
}
return pNode;
}
and:
CSubNode * CSerializeExpDoc::AddNewSubNode()
{
CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
CSubNode *pSubNode = new CSubNode();
try
{
curNode->m_oaSubNodes.Add(pSubNode);
SetModifiedFlag();
m_iSubNodePosition = (curNode->m_oaSubNodes.GetSize()-1);
}
catch (CMemoryException* perr)
{
AfxMessageBox("Out of Memory",MB_ICONSTOP | MB_OK);
if (pSubNode)
{
delete pSubNode;
pSubNode = NULL;
}
perr->Delete ();
}
return pSubNode;
}
To save time, I am going to simply copy all of the functions here. You can add them to your project using the ClassView. They are all public
. The type and declaration for each function can be gleaned from its implementation, as written out below. For instance, for the function 'GetCurRecord
', the type is 'CNode *
' and the declaration is 'GetCurRecord()
'.
CNode* CSerializeExpDoc::GetCurRecord()
{
if (m_iCurPosition >=0)
return (CNode*)m_oaNodes[m_iCurPosition];
else
return NULL;
}
CNode* CSerializeExpDoc::GetFirstRecord()
{
if (m_oaNodes.GetSize() > 0)
{
m_iCurPosition =0;
m_iSubNodePosition = 0;
GetFirstSubNode();
return (CNode*)m_oaNodes[0];
}
else
return NULL;
}
CNode* CSerializeExpDoc::GetNextRecord()
{
m_iSubNodePosition = 0;
if (++m_iCurPosition < m_oaNodes.GetSize()){
return (CNode*)m_oaNodes[m_iCurPosition];
}
else
return AddNewRecord();
}
CNode* CSerializeExpDoc::GetPrevRecord()
{
if (m_oaNodes.GetSize() > 0)
{
m_iSubNodePosition = 0;
if (--m_iCurPosition < 0){
m_iCurPosition = 0;
}
return (CNode*)m_oaNodes[m_iCurPosition];
}
else
return NULL;
}
CNode * CSerializeExpDoc::GetLastRecord()
{
if (m_oaNodes.GetSize() > 0)
{
m_iSubNodePosition = 0;
m_iCurPosition = (m_oaNodes.GetSize() - 1);
m_iSubNodePosition = 0;
return (CNode*)m_oaNodes[m_iCurPosition];
}
else
return NULL;
}
int CSerializeExpDoc::GetCurRecordNbr()
{
return (m_iCurPosition +1);
}
CSubNode* CSerializeExpDoc::GetFirstSubNode()
{
CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
if (curNode->m_oaSubNodes.GetSize() > 0)
{
m_iSubNodePosition =0;
return (CSubNode*)curNode->m_oaSubNodes[0];
}
else
return NULL;
}
CSubNode* CSerializeExpDoc::GetNextSubNode()
{
CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
if (++m_iSubNodePosition < curNode->m_oaSubNodes.GetSize())
return (CSubNode*)curNode->m_oaSubNodes[m_iSubNodePosition];
else
return AddNewSubNode();
}
CSubNode* CSerializeExpDoc::GetPrevSubNode()
{
CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
if (curNode->m_oaSubNodes.GetSize() > 0)
{
if (--m_iSubNodePosition < 0)
m_iSubNodePosition = 0;
return (CSubNode*)curNode->m_oaSubNodes[m_iSubNodePosition];
}
else
return NULL;
}
int CSerializeExpDoc::GetTotalSubNodes()
{
CNode * curNode = (CNode *) m_oaNodes[m_iCurPosition];
return curNode->m_oaSubNodes.GetSize();
}
int CSerializeExpDoc::GetCurSubNodeNbr()
{
return (m_iSubNodePosition + 1);
}
Cleaning up the document
In C++, you need to make sure that all of the objects you create are properly deleted when you don't need them any more, or your application may cause a problem known as 'memory leak'. The following function is an event handler which is called when the document is closed or a new document is opened. It is a pair of nested loops, one for looping through the nodes and deleting them, and an inner loop for looping through the sub nodes contained within each node object and deleting them. You need to use the Class Wizard for creating this function. Click on the 'Message Maps' tab. Make sure the document class' class name is selected in the drop-down list, then double click on 'DeleteContents' in the 'Messages' list to generate the function.
Fill out the body of the function with the following code...
void CSerializeExpDoc::DeleteContents()
{
int noCount = m_oaNodes.GetSize();
int noPos;
if (noCount)
{
for (noPos = 0; noPos < noCount; noPos++){
CNode * pCNode = (CNode *)m_oaNodes[noPos];
int snCount = pCNode->m_oaSubNodes.GetSize();
int snPos;
if (snCount)
{
for (snPos = 0; snPos < snCount; snPos++)
delete pCNode->m_oaSubNodes[snPos];
pCNode->m_oaSubNodes.RemoveAll();
}
delete m_oaNodes[noPos];
}
m_oaNodes.RemoveAll();
}
CDocument::DeleteContents();
}
Before leaving the document class implementation file, scroll up to the top and add a '#include
' line for the view class so that the top of the file now looks something like this...
#include "stdafx.h"
#include "SerializeExp.h"
#include "Node.h"
#include "SubNode.h"
#include "SerializeExpDoc.h"
#include "SerializeExpView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
Displaying a new record set
At this point, create a private
function in the View class of type void
, declared as PopulateView()
. We'll put in the code later.
Create two new private
member variables for the View class. One of type CNode*
, called m_nCurNode
, and another of type CSubNode*
, called m_snCurSubNode
.
Now, make another public function in the View class of type void
, declared as ShowFirst()
. This function is executed when a document is opened.
Put the following code in it...
void CSerializeExpView::ShowFirst()
{
CSerializeExpDoc* pDoc = GetDocument();
if (pDoc)
{
m_nCurNode = pDoc->GetFirstRecord();
m_snCurSubNode = pDoc->GetFirstSubNode();
if (m_nCurNode)
{
PopulateView();
}
}
}
What to do when a new document is opened
Instead of the document class calling ShowFirst
directly, it calls the NewDataSet
function in the view class. You need to create this function. It is another public
function of type void
.
Implement NewDataSet
as follows...
void CSerializeExpView::NewDataSet()
{
ShowFirst();
}
The OnNewDocument
function creates the new document, creates fresh, empty records ready for new information, and clears the current view. The function should already exist in the document class. You need to add code to it as follows...
BOOL CSerializeExpDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
if (!AddNewRecord())
return FALSE;
POSITION pos = GetFirstViewPosition();
CSerializeExpView* pView = (CSerializeExpView*) GetNextView(pos);
if (pView)
pView->NewDataSet();
return TRUE;
}
Displaying the current record
This function displays data from the document in the view. Go back to the function CSerializeExpDoc::PopulateView
you created earlier, and fill it with the following code...
void CSerializeExpView::PopulateView()
{
CSerializeExpDoc* pDoc = GetDocument();
if (pDoc)
{
m_sNPosition.Format("Node information\n\nRecord %d of %d",
pDoc->GetCurRecordNbr(),pDoc->GetTotalRecords());
m_sSPosition.Format("Sub Node information\n\nRecord %d of %d",
pDoc->GetCurSubNodeNbr(),pDoc->GetTotalSubNodes());
if (m_nCurNode)
{
m_sNod = m_nCurNode->GetString();
if (m_snCurSubNode){
if (pDoc->GetTotalSubNodes()>0){
m_sSubNod = m_snCurSubNode->GetString();
m_shortSubNodeWeight =
m_snCurSubNode->GetWeight();
}
}
}
}
UpdateData (FALSE);
}
Before the project will compile, you need to add:
- the
GetTotalRecords()
function to the document class, and #include "Node.h"
to the view class implementation file
Add the function GetTotalRecords()
to the document class. It is of type int
and is public
. Implement it as follows...
int CSerializeExpDoc::GetTotalRecords()
{
return m_oaNodes.GetSize();
}
Add the line #include "Node.h"
near the top of the view class implementation file so that the include section now looks like...
#include "stdafx.h"
#include "SerializeExp.h"
#include "Node.h"
#include "SerializeExpDoc.h"
#include "SerializeExpView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
Navigating the record set
The application needs to have functions added which respond to user input in the View class. These are called 'event handlers', and are most easily created using the ClassWizard. Open the ClassWizard, click on the Message Maps tab, make sure the View class is selected in the 'Class name:' drop down list, and then for each of the Object IDs IDC_BNODLEFT
, IDC_BNODRIGHT
, IDC_BSUBNODLEFT
, and IDC_BSUBNODRIGHT
, double click on the 'BN_CLICKED
' message and change the suggested member function name to the following in turn...
OnBNodeLeft
OnBNodeRight
OnBSubNodeLeft
OnBSubNodeRight
Now implement the functions as follows...
void CSerializeExpView::OnBNodeLeft()
{
CSerializeExpDoc * pDoc = GetDocument();
if (pDoc)
{
m_nCurNode = pDoc->GetPrevRecord();
pDoc->GetFirstSubNode();
OnBSubNodeLeft();
if (m_nCurNode)
{
PopulateView();
}
}
}
void CSerializeExpView::OnBNodeRight()
{
CSerializeExpDoc * pDoc = GetDocument();
if (pDoc)
{
m_nCurNode = pDoc->GetNextRecord();
pDoc->GetFirstSubNode();
OnBSubNodeLeft();
if (m_nCurNode)
{
PopulateView();
}
}
}
void CSerializeExpView::OnBSubNodeLeft()
{
CSerializeExpDoc * pDoc = GetDocument();
if (pDoc)
{
m_snCurSubNode = pDoc->GetPrevSubNode();
if (m_snCurSubNode)
{
PopulateView();
}
}
}
void CSerializeExpView::OnBSubNodeRight()
{
CSerializeExpDoc * pDoc = GetDocument();
if (pDoc)
{
m_snCurSubNode = pDoc->GetNextSubNode();
if (m_snCurSubNode)
{
PopulateView();
}
}
}
What to do when opening a saved document
You need to create an event handler function for the OnOpenDocument
message in the Document class. Implement this function as follows...
BOOL CSerializeExpDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
POSITION pos = GetFirstViewPosition();
CSerializeExpView* pView =
(CSerializeExpView*) GetNextView(pos);
if (pView)
pView->NewDataSet();
return TRUE;
}
Saving edits and changes
All that is left to do now is to place event handlers for the edit box controls. Using the ClassWizard for the View class, select the corresponding Object IDs for each of the edit boxes, then double click on the EN_CHANGE
message for each of the edit boxes. The ClassWizard then suggests a function name for each event handler. You can change this into something more meaningful if need be.
For IDC_ENOD
, the ClassWizard suggests OnChangeEnod
as an event handler name. I suggest using OnChangeENode
instead. For IDC_ESNWEIGHT
, instead of using OnChangeWsnweight
, I suggest using OnChangeWSNWeight
, and for IDC_ESUBNOD
, instead of using OnChangeEsubnod
, I suggest using OnChangeESubNode
.
Implement these functions as follows...
void CSerializeExpView::OnChangeENode()
{
UpdateData(TRUE);
if (m_nCurNode)
m_nCurNode->SetString(m_sNod);
}
void CSerializeExpView::OnChangeESNWeight()
{
UpdateData(TRUE);
if (m_snCurSubNode)
m_snCurSubNode->SetWeight(m_shortSubNodeWeight);
}
void CSerializeExpView::OnChangeESubNode()
{
UpdateData(TRUE);
if (m_snCurSubNode)
m_snCurSubNode->SetString(m_sSubNod);
}
Memory management note...
For better memory management and efficiency, use CObArray
's SetSize
function. This can prevent fragmentation and unnecessary copying.
References
- Much of this article is inspired by and based on 'Teach Yourself Visual C++ 6 in 21 Days', Chapter 13: 'Saving and Restoring Work--File Access' by Davis Chapman.
- I got plenty more inspiration and pleasure reading A serialization primer - Part 3, by Ravi Bhavnani.