Dale Rogerson
July 20, 1995
Click to open or copy the files in the TemMemPtr sample application.
Pointers to member functions are used to implement member function callbacks, which can reside in any class due to the use of templates.The TemMemPtr sample application illustrates the concept.
Using callbacks is one of the most powerful ways to customize an object. The traditional example is the qsort function, which takes a pointer to a function that compares two objects. However, qsort only works if the function is not a member of a class.
I recently wrote an application containing a customizable dialog box. However, unlike the normal C-style callbacks used by qsort and in most Win32®-based applications, I used C++ member functions to implement these callbacks. By cleverly using templates, the dialog box could call member functions in any C++ class, including my CView- and CDocument-derived classes.
Callbacks are implemented with pointers to functions: A pointer to the callback is passed to a function, which then calls the callback by means of the pointer. If you want to use a member function as a callback, you need to use a pointer to the member function.
Let’s take a closer look at pointers to member functions. Take, for example, the following CFoo class:
class CFoo {
public:
void f(int) ;
} ;
Let’s call CFoo::f through a pointer. Below, I have defined MemFntPtr as a pointer to a CFoo member function taking an integer and returning void:
typedef void (CFoo::*MemFntPtr)(int) ;
Then I declare a pointer to a member function and initialize it with the address of CFoo::f:
MemFuncPtr ptr = &CFoo::f ;
The following code fragment shows how to call CFoo::f:
CFoo aFoo ;
CFoo* pFoo = &aFoo ;
// Call f directly.
aFoo.f(5) ;
// Call f via an object pointer.
pFoo->f(5) ;
// Call f using member function pointer.
(aFoo.*ptr)(5) ;
// Call f using member function pointer
// and an object pointer.
(pFoo->*ptr)(5) ;
At first glance, pointers to member functions look very different from pointers to functions. However, the table below shows that pointers to member functions are similar syntactically to pointers to functions.
Pointer to Function | Pointer to Member Function | |
|
|
|
|
|
|
|
|
Pointers to member functions can point to virtual members with all the associated benefits. Consider the following definitions:
class CAircraft {
public:
virtual void TakeOff() ;
virtual void Fly() ;
virtual void Land() ;
} ;
class CAirplane : public CAircraft {
public:
virtual void TakeOff() ;
virtual void Fly() ;
virtual void Land() ;
} ;
class CHelicopter : public CAircraft {
public:
virtual void TakeOff() ;
virtual void Fly() ;
virtual void Land() ;
} ;
Let's create a helicopter and a plane (boy, don't you wish it were this easy!):
CHelicopter aHeli ;
CAirplane aPlane ;
Now, let’s take off in the helicopter. We define Maneuver to be a pointer to a member function without parameters and returning void. The following statements define pManeuver as a pointer to CAircraft::TakeOff and pCraft as a CAircraft pointer pointing to aHeli.
typedef void (CAircraft::*Maneuver)() ;
Maneuver pManeuver = &CAircraft::TakeOff ;
CAircraft* pCraft = &aHeli ;
Now we are ready to take off in our helicopter!
(pCraft->*pManeuver)() ;
To take off in our plane, we just change the value of pCraft to the address of aPlane and use the same statement we used previously:
pCraft = &aPlane ;
(pCraft->*pManeuver)() ;
To land our plane and helicopter, we change the value of pManeuver to the address of CAircraft::Land:
aManeuver = &CAircraft::Land ;
(pCraft->*pManeuver)() ; // Land the plane. . .
pCraft = &aHeli ;
(pCraft->*pManeuver)() ; // and helicopter.
Using pointers to virtual member functions is a powerful technique and can lead to some dynamic solutions to programming problems. The solution above is useful in programs in which a user selects an object (picture, sound, or video) and an operation to perform on that object ( play, copy, delete, and so on). The code to do the operation can be as simple as:
(pSelectedObject->*pSelectedOperation)() ;
The Microsoft Foundation Class Library (MFC) message maps use pointers to member functions to route messages to the appropriate function. Just look at the MFC CWnd::WndProc function.
I started this article talking about callbacks. As an example, let’s say that I want my application to customize certain controls in a dialog box. So, I pass a callback to the dialog box, and the dialog box will call the callback with a pointer to one of its controls. The callback can then customize the control.
Below is the definition of the document class with the callback member function for customizing the dialog box.
class CMyDoc : public CDoc {
.
.
.
// Callback.
void CalledByDialog(CWnd* pWnd) ;
// Initalize and call dialog.
void OnCallitFromdoc() ;
.
.
.
} ;
The dialog box is declared as follows:
class CTheDialog : public CDialog
{
.
.
.
public:
// Set object containing the callback.
void SetObjectPtr(CMyDoc* ptr)
{m_ptrObject = ptr; }
// Define callback's signature.
typedef void (CMyDoc::*PtrMemFnt)(CWnd*);
// Set which function is the callback.
void SetPtrMemFnt(PtrMemFnt ptr)
{m_ptrMemFnt = ptr; }
protected:
// Store pointers.
CMyDoc* m_ptrObject ;
PtrMemFnt m_ptrMemFnt ;
// Call the callback.
virtual void CallMemberPtr(CWnd* pWnd)
{ (m_ptrObject->*m_ptrMemFnt)(pWnd) ;}
.
.
.
BOOL CTheDialog::OnInitDialog() ;
.
.
.
};
SetObjectPtr tells the dialog box which object contains the member function callback. SetPtrMemFnt tells the dialog box which function in the object is the callback. PtrMemFnt defines which type of functions can be used as callbacks. In this case, CMyDoc member functions taking a CWnd pointer and returning a void can be callbacks.
Let’s follow the execution path to see what happens. First, CMyDoc::OnCallitFromdoc creates the dialog box and passes the object pointer and the member function pointer for our callback to the dialog box.
void CMyDoc::OnCallitFromdoc()
{
CTheDialog aDlg ;
aDlg.SetObjectPtr(this) ;
aDlg.SetPtrMemFnt(&CMyDoc::CalledByDialog) ;
aDlg.DoModal() ;
}
When the dialog box starts, OnInitDialog calls CallMemberPtr.
BOOL CTheDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CallMemberPtr() ; // Call the callback.
return TRUE;
}
CallMemberPtr calls the callback with the statement:
(m_ptrObject->*m_ptrMemFnt)(pWnd);
The callback is defined in CMyDoc as:
void CMyDoc::CalledByDialog(CWnd* pWnd)
{
pWnd->SetWindowText(
"Hello from the Doc class.") ;
}
Now we have a dialog box that can be customized by callbacks in CMyDoc.
But there’s a problem with this implementation of a callback. CTheDialog can only be customized by a CMyDoc object. Consider the following code:
class CMyView : public CView {
.
.
.
// Callback.
void CalledByDialog(CWnd* pWnd) ;
// Initalize and call dialog.
void OnCallitFromview() ;
.
.
.
} ;
void CMyView::OnCallitFromview()
{
CTheDialog aDlg ;
// The following does not work
// because CMyView != CMyDoc.
aDlg.SetObjectPtr(this);
aDlg.SetPtrMemFnt(&CViewDoc::CalledByDialog);
aDlg.DoModal() ;
}
CMyView::OnCallitFromview as shown above will not work. CTheDialog::SetObjectPtr expects a CMyDoc pointer, and CTheDialog::SetPtrMemFnt expects a pointer to a member function in CMyDoc.
typedef void (CMyDoc::*PtrMemFnt)(CWnd* pWnd);
So how can my dialog box invoke callbacks in both CMyDoc and CMyView? When you want to do something with multiple incompatible types, it’s time to look at templates.
A class can be thought of as a recipe from which objects are created. A class template is a recipe from which classes are created. Using templates is a form of meta-programming; the compiler uses the template to generate code customized for a particular type.
For the template solution, I moved all of the code supporting callbacks from CTheDialog into a new template class. This new template class, CTemplateDialog, is derived from CTheDialog.
class CTheDialog : public CDialog
{
.
.
.
// Implementation.
protected:
virtual void CallMemberPtr(CWnd*) = 0 ;
BOOL CTheDialog::OnInitDialog() ;
.
.
.
};
A pure virtual version of CallMemberPtr is left in CTheDialog so that the implementation of CTheDialog::OnInitDialog remains the same.
All of the callback code removed from CTheDialog was placed in CTemplateDialog, which inherits from CTheDialog. Below is the definition for the CTemplateDialog template class. The template takes one parameter, T, which it uses to fill out the template when generating code. The compiler will replace all T’s in the code below with the appropriate class.
template<class T>
class CTemplateDialog : public CTheDialog
{
public:
void SetObjectPtr(T* ptr)
{m_ptrObject = ptr;}
typedef void (T::*PtrMemFnt)(CWnd*) ;
void SetPtrMemFnt(PtrMemFnt ptr)
{m_ptrMemFnt = ptr; }
protected:
T* m_ptrObject ;
PtrMemFnt m_ptrMemFnt ;
virtual void CallMemberPtr(CWnd* pWnd)
{ (m_ptrObject->*m_ptrMemFnt)(pWnd) ;}
};
For example, if you use:
CTemplateDialog<CMyDoc> aDlg ;
all of the T’s will be replaced with CMyDoc, generating the following code:
.
.
.
void SetObjectPtr(CMyDoc* ptr)
{m_ptrObject = ptr; }
typedef void (CMyDoc::*PtrMemFnt)(CWnd*) ;
.
.
.
CMyDoc* m_ptrObject ;
.
.
.
It seems a lot like macros, doesn’t it? But this is typesafe and completely supported by the compiler.
The callback is initialized and the dialog box is still executed from the CMyDoc::OnCallitFromdoc function, but now CTemplateDialog is used instead of CTheDialog.
void CMyDoc::OnCallitFromdoc()
{
CTemplateDialog<CMyDoc> aDlg ;
aDlg.SetObjectPtr(this) ;
aDlg.SetPtrMemFnt(&CMyDoc::CalledByDialog) ;
aDlg.DoModal() ;
}
Now we can also customize the dialog box from our CMyView class:
void CMyView::OnCallitFromview()
{
CTemplateDialog<CMyView> aDlg ;
aDlg.SetObjectPtr(this) ;
aDlg.SetPtrMemFnt(&CMyView::CalledByDialog) ;
aDlg.DoModal() ;
}
Templates and pointers to member functions are two very powerful features of C++. Using these two features in conjunction results in even more powerful solutions to programming problems.