UCanCode
Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database
workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!
"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"
|
|
C++
Article: STL
Iterator and the VC++ MFC Iterator
Dave Lorde.
What's It All About?
This article
is an introduction to STL-style
iterators, and how
by writing your own iterators,
the use of certain containers can be simplified and made
STL compatible. The
containers used in this example
are the MFC framework
application, document template, and document classes (CWinApp,
CDocTemplate, and CDocument), which form a hierarchy
with the lowest level, the document, containing views (CView).
If you can answer 'Yes'
to one or more of the following questions, this article
may be interesting (or even useful) to you:
Are you ever irritated
by the GetFirstPosition / GetNextItem style of iteration
through MFC's window containers?
Does 'POSITION' leave
you cold?
Do you have a nagging
feeling that there must be a simpler way?
Have you thought about
using the STL, but
can't see much use for it with MFC?
Do you use the STL,
but wish MFC classes
could be more involved?
Have you ever wondered
what iterators
really do, and how they can be useful?
Would you like to see
how to construct a single iterator
that can return you a pointer to every View in your
application?
STL
Iterators
STL
iterators are used to access items held in STL
containers. A C++
pointer is a kind of iterator
which can be used to access items in an array. Other STL
iterators are generally class objects that
have similar operations to C++ pointers,
such as ++ (increment) and * (dereference). They are
arranged in a hierarchy (not an inheritance hierarchy)
according to the operations they support, so the most
restricted Input and Output iterators support
incrementing with ++, dereferencing with *, and equality
checking with == and !=. Input iterators only read the
elements, Output iterators
only write them. They are typically used with input and
output streams. Next come Forward iterators, which
combine the read and write capabilities of Input and
Output iterators,
and Bidirectional iterators
are Forward iterators that can also go backwards using
the -- (decrement) operator. Random Access iterators
can do all this, and also use the [] operator to
randomly access items by some key or index. C++
pointers are Random Access iterators.
STL
containers generally define their own iterators
as class members, and provide a begin() function to get
an iterator to the first element, and an end() function
that returns an iterator *past* the end of the
container. The end iterator doesn't point to a valid
item, but is only used to test whether another iterator
has reached the end of the container, using the !=
operator, typically like this:
typedef vector < int > IntArray;
IntArray myArray;
IntArray::iterator vi; // construct iterator for an array of int
for (vi = myArray.begin(); vi != myArray.end(); ++vi)
{
// Note that the loop test is always: iterator != end(), never: iterator > end()
}
The Main Bit
In this article I
describe a template class that will make an STL-style
input iterator for any class that contains elements
accessible by GetFirstPosition / GetNextItem style
member functions, and present some classes that use it
to make iterators for MFC CViews, CDocuments, and
CDocTemplates. I also show a class that will permit
these iterators to be nested, allowing such joys as
iteration over all the CViews of all the CDocuments for
a CDocTemplate, and even all the CViews of all the
CDocuments for all the CDocTemplates in a CWinApp.
Because the MFC
containers don't have a begin() function to return an
iterator, my iterators set themselves to the first item
on construction, and they contain their own end()
function to test for reaching the end of the container.
For example,
the traditional MFC
iteration of Views might be something like this:
void CMyDoc::OnRepaintAllViews()
{
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
CView* pView = GetNextView(pos);
pView->UpdateWindow();
}
}
using the ViewIterator it
becomes:
void CMyDoc::OnRepaintAllViews()
{
for (ViewIter vi(this); vi != vi.end(); ++vi)
{
(*vi)->UpdateWindow();
}
}
The MFC
code to get the first View for a Document usually looks
like this:
POSITION pos = pDoc->GetFirstViewPosition();
CView* pView = pDoc->GetNextView(pos);
using the ViewIterator it
becomes:
ViewIter vIt(pDoc); // Construct view iterator - points to first view
CView* pView = *vIt; // dereference iterator to get view pointer
which can be shortened to:
CView* pView = *ViewIter(pDoc); // Construct temporary ViewIter and dereference it
The other iterators
for CDocuments and and CDocTemplates work in the same
way. Because the base class for these iterators is
templated with the GetFirstxxx/GetNextxxx functions, it
is flexible enough to make iterators for other common
classes with suitable functions, such as the CTreeCtrl
with GetFirstVisibleItem() and GetNextVisibleItem().
Here is the iterator
base class, BaseMFCIter:
#ifndef BASEMFCITERATOR_H
#define BASEMFCITERATOR_H
//***************************************************************************/
//
// BaseMFCIter class implementation.
//
// Base iterator class for iterating MFC-style containers that use GetFirstPos,
// GetNextItem semantics (where GetFirstPos returns a value to be passed to
// GetNextItem, which updates it and returns an item).
//
// NOTE: The Item type must have a default constructor if it is not a basic type.
//***************************************************************************
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include < iterator >
// Define BaseMFCIter as a standard input iterator.
//
// The template arguments are:
// Item: the contained element type
// Cont: the container type
// Key: the access key (defaults to POSITION)
template < class Item, class Cont, class Key = POSITION >
class BaseMFCIter : public std::iterator < std::input_iterator_tag, Item >
{
public:
// Define types for the 2 member functions to be used:
typedef Key (Cont::*GetFirstFunctionPtr) () const;
typedef Item (Cont::*GetNextFunctionPtr) (Key&) const;
// Default constructor, makes a null iterator, equal to BaseMFCIter::end()
BaseMFCIter() : m_pCont(0), m_Pos(0), m_GetFirstFunc(0), m_GetNextFunc(0), m_End(true) {}
// Constructor taking pointer to container and the iteration functions
BaseMFCIter(Cont* pCont, GetFirstFunctionPtr pFF, GetNextFunctionPtr pNF)
: m_pCont(pCont), m_Pos(0), m_GetFirstFunc(pFF), m_GetNextFunc(pNF)
{ init(); }
// Copy constructor, initialises iterator to first element
BaseMFCIter(const BaseMFCIter& vi) : m_pCont(vi.m_pCont), m_Pos(0),
m_GetFirstFunc(vi.m_GetFirstFunc), m_GetNextFunc(vi.m_GetNextFunc)
{ init(); }
// Assignment operator, initialises iterator to first element
BaseMFCIter& operator=(const BaseMFCIter& vi)
{
m_pCont = vi.m_pCont;
m_GetFirstFunc = vi.m_GetFirstFunc;
m_GetNextFunc = vi.m_GetNextFunc;
init();
return *this;
}
bool operator == (const BaseMFCIter& rhs) const
{ return (m_Pos == rhs.m_Pos && m_End == rhs.m_End); }
bool operator != (const BaseMFCIter& rhs) const
{ return !operator==(rhs); }
BaseMFCIter& operator ++ () { advance(); return *this; }
BaseMFCIter& operator ++ (int) { BaseMFCIter ret(*this); advance(); return ret; }
Item operator * () { return m_Item; }
Item operator -> () { return m_Item; }
static BaseMFCIter end () { return BaseMFCIter(); } // end() returns default null iterator
private:
Item m_Item; // Current item from container
Cont* m_pCont; // Pointer to container
Key m_Pos; // Key to item in container
bool m_End; // Flag to indicate end of container reached
// Pointers to container iteration functions
GetFirstFunctionPtr m_GetFirstFunc;
GetNextFunctionPtr m_GetNextFunc;
// Use container GetFirst & GetNext functions to set to first element, or end() if not found
void init()
{
m_Pos = 0;
m_End = true;
if (m_pCont && m_GetFirstFunc != 0)
{
m_Pos = (m_pCont->*m_GetFirstFunc)();
advance();
}
}
// Use container GetNext function to find next element in container
void advance()
{
m_End = m_Pos ? false : true;
m_Item = (m_Pos && m_pCont && m_GetNextFunc != 0) ?
(m_pCont->*m_GetNextFunc)(m_Pos) : Item();
}
};
#endif
BaseMFCIter (above) is
templated to take the item type to be returned, the
container type that holds the items, and the key type
used to access the items in the container (defaulted to
POSITION). BaseMFCIter also needs to hold pointers to
the container functions it must call to get the first
item position and next item. These function pointers
should be passed to the BaseMFCIter constructor along
with a pointer to the container to be used. If you
haven't used pointers to member functions before, you
will see they're really not that difficult to use.
Here are the iterator
classes, derived from BaseMFCIter. Notice how simple
they are, just initialising the base class with the
appropriate container and the functions to use on it:
#ifndef MFCITERATORS_H
#define MFCITERATORS_H
//***************************************************************************/
//
// ViewIter, DocIter, and DocTemplateIter class implementations.
//
// Iterator classes for iterating views, documents, and doctemplates.
//
//***************************************************************************
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "BaseMFCIter.h"
class ViewIter : public BaseMFCIter < CView*, CDocument >
{
public:
ViewIter(CDocument* pDoc = 0) : BaseMFCIter< CView*, CDocument >
(pDoc, CDocument::GetFirstViewPosition, CDocument::GetNextView)
{}
};
class DocIter : public BaseMFCIter< CDocument*, CDocTemplate >
{
public:
DocIter(CDocTemplate* pDT = 0) : BaseMFCIter< CDocument*, CDocTemplate >
(pDT, CDocTemplate::GetFirstDocPosition, CDocTemplate::GetNextDoc)
{}
};
class CDocTemplateIter : public BaseMFCIter< CDocTemplate*, CWinApp >
{
public:
CDocTemplateIter(CWinApp* pApp = 0) : BaseMFCIter< CDocTemplate*, CWinApp >
(pApp, CWinApp::GetFirstDocTemplatePosition, CWinApp::GetNextDocTemplate)
{}
};
#endif
Nesting Iterators
So far so good. We now
have convenient iterators
to get doctemplates from applications, documents from
doctemplates, and views from documents. It should now
also be fairly clear how you can use BaseMFCIter to make
other iterators that
fit this GetFirstPos / GetNext idiom.
Interestingly, the
containers for these particular iterators
(above) form a nested hierarchy: documents within
doctemplates within applications. It might be useful if
we could somehow combine them so as to be able iterate
over, for example, all the documents in all the
doctemplates in an application, without having to code a
nested loop to do it.
The solution,
unsurprisingly, is another templated iterator class that
wraps two appropriate iterators
and presents them as one. I have called it NestedMFCIter,
and it is templated to take an inner iterator
which accesses the items we are interested in, an outer
iterator that accesses the containers holding the items,
and an outer container that holds those containers. I'm
sorry if that's less than not very clear, but it's a
little hard to describe clearly... anyway, this is the
class:
#ifndef NESTEDMFCITERATOR_H
#define NESTEDMFCITERATOR_H
//****************************************************************************
//
// NestedMFCIter class implementation.
//
// Iterator class for iterating through the contents of nested containers
// using iterators for each container type.
//
// class OuterCont is the container that holds the collections iterated over by
// class OuterIter. OuterIter dereferences to a container of items iterated over
// by class InnerIter. NestedMFCIter itself dereferences to the item iterated by
// InnerIter, thus allowing iteration over every item in every container in OuterCont.
//
//***************************************************************************
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
template < class InnerIter, class OuterIter, class OuterCont >
class NestedMFCIter : public std::iterator < std::input_iterator_tag, InnerIter::value_type >
{
public:
NestedMFCIter(OuterCont* pOC = 0) :
m_OuterIt(pOC), m_InnerIt(pOC ? *m_OuterIt : 0)
{}
NestedMFCIter& operator=(OuterCont* pOC)
{ m_OuterIt = pOC; m_InnerIt = *m_OuterIt; return *this; }
bool operator == (const NestedMFCIter& rhs) const
{ return m_InnerIt == rhs.m_InnerIt; }
bool operator != (const NestedMFCIter& rhs) const
{ return !operator==(rhs); }
NestedMFCIter& operator ++ () { advance(); return *this; }
NestedMFCIter& operator ++ (int)
{ NestedMFCIter ret(*this); advance(); return ret; }
InnerIter::value_type operator*() { return *m_InnerIt; }
InnerIter::value_type operator->() { return *m_InnerIt; }
static NestedMFCIter end() { return NestedMFCIter(); }
private:
OuterIter m_OuterIt;
InnerIter m_InnerIt;
// Advance to next inner item. If null, advance outer iterator to next inner
void advance()
{
if (m_InnerIt != InnerIter::end())
++m_InnerIt;
while (m_InnerIt == InnerIter::end() && (++m_OuterIt) != OuterIter::end())
{
m_InnerIt = *m_OuterIt;
}
}
};
#endif
As you can see, it is
constructed with a pointer to the outer container, and
iterates over the contents of all the containers held in
the outer container. For example, to extract the titles
of all the documents in all the doctemplates in an
application:
// define a convenient type name
typedef NestedMFCIter < DocIter, DocTemplateIter, CWinApp > AppDocIter;
for (AppDocIter adi(AfxGetApp()); adi != adi.end(); ++adi)
{
CString title = (*adi)->GetTitle();
...
}
Here, we template
NestedMFCIter with the types of the inner iterator:
DocIter, the outer iterator:
DocTemplateIter, and the outer container: CWinApp. When
constructed with an application instance, the resulting
iterator will find the first CDocument in the first
CDocTemplate in the application. When advanced, it will
iterate through each of the CDocuments in each of the
CDocTemplates in turn.
If this isn't enough,
you can even use a NestedMFCIter type as one of the
iterators passed to another NestedMFCIter type. This
allows you to iterate over the items at the bottom of
arbitrarily deep nestings of containers. For example, to
access the window handles of all the views in an
application, you can create a NestedMFCIter iterator for
all the views in a doctemplate, and use that together
with an iterator that accesses doctemplates in an
application, in another NestedMFCIter:
typedef NestedMFCIter < ViewIter, DocIter, CDocTemplate > DocTemplateViewIter;
typedef NestedMFCIter< DocTemplateViewIter, CDocTemplateIter, CWinApp > AppViewIter;
for (AppViewIter avi(AfxGetApp()); avi != avi.end(); ++avi)
{
HWND hWnd = (*avi)->GetSafeHwnd(); // Extract hWnd of view
...
}
This version of a nested iterator
type relies on the sort of iterators
I have provided for the MFC
classes discussed, but it would not be difficult to
modify it to use the STL
containers and iterators provided by Microsoft. This
would allow simplified traversal of all the elements in
multi-dimensional arrays made with nested STL
containers.
So What's the Big
Deal?
These MFC
iterators are quite elegant in their own way,
but let's face it, they don't really save more than a
couple of lines of code, and they take up more than that
behind the scenes. Why go to all this trouble just to
avoid using POSITION ?
Well, firstly, this is
just an example by way of an introduction to
roll-your-own iterators
to show what can be done. However, the real answer is
that the use of iterators
is the key to the STL.
Any container that has STL
iterators has a huge range of functions and
algorithms available in the STL for manipulating it's
contents. A typical STL algorithm or function will take
a start iterator, an
end iterator, an optional predicate function, and will
perform some operation involving the elements from the
start up to (but not including) the end. Where a
predicate function is supplied, the operation performed
will use the predicate function.
A predicate function
can be an ordinary file-scope function, or a class
operator() function. The latter is used where the
function needs access to some data that persists between
function calls. Rather than use static data, class
member data is used, which can be initialised
appropriately when the class is constructed. If this
still doesn't make much sense, maybe an example will
help:
// Setting all application views to a desired display state (normal, maximised, iconised, etc.):
#include < algorithm >
#include "NestedMFCIterator.h"
#include "MFCIterators.h"
using std::for_each; // Declare that we're using the std library 'for_each' algorithm
// Create an iterator for all views (as previously described)
typedef NestedMFCIter < ViewIter, DocIter, CDocTemplate > DocTemplateViewIter;
typedef NestedMFCIter < DocTemplateViewIter, CDocTemplateIter, CWinApp > AppViewIter;
// Define a predicate class to set the parent frame of a view to a desired show state
class DoShowWindow {
public:
DoShowWindow(int nCmdShow) : m_CmdShow(nCmdShow) {} // constructor
// Predicate function
void operator()(CView* pView) { pView->GetParentFrame()->ShowWindow(m_CmdShow); }
private:
int m_CmdShow; // Stores the desired show state
};
// Function that uses DoShowWindow to set the state of all views:
void MyFunc()
{
CWinApp* pApp = AfxGetApp();
// Show all views normal
for_each(AppViewIter(pApp), AppViewIter::end(), DoShowWindow(SW_SHOWNORMAL));
...
// Show all views minimised
for_each(AppViewIter(pApp), AppViewIter::end(), DoShowWindow(SW_SHOWMINNOACTIVE));
...
}
Download
the demo project (VC++6)
for an example, showing the use of all the classes
discussed, to minimise or restore the child windows in
an MDI application with multiple document templates,
documents, and views.
Summary
In this article, I have
introduced STL iterators
and shown how writing your own iterators
for non-STL
containers can help simplify their use and make them
more compatible with STL
algorithms. The examples used provide templated iterator
classes for MFC containers with GetFirst/GetNext
iteration syntax, and have been expanded to show how
they may be used in combination to provide nested
container iteration.
Download
demo project - 26 KB
Download
source - 3 KB
|