by Kaare Christian
Kaare Christian is a research associate at Rockefeller University. He is a frequent contributor to PC Magazine and is the author of the forthcoming Microsoft Guide to C++ Programming (Microsoft Press).
QI’m building a simple graphical user interface library using C++. Yes, I’ve heard of the MicrosoftÒ WindowsÔ operating system and the Microsoft Foundation Classes (MFC) but I prefer to roll my own! One of the classes in my class hierarchy keeps track of all the others, and I need some way to guarantee that there is only one copy of this object. Is there a C++ feature that can help?
AYours is a common problem. I’m familiar with an application that uses a single object of a lock manager class to implement shared memory, and I’ve used single objects for data acquisition myself.
There isn’t a C++ keyword that says "only allow one such object" but you can take advantage of the fact that C++ permits the use of the private and public access specifiers for member functions, even constructors. If your single object does not rely on any initialization information determined at run time, declare the class object within itself as public and use a private constructor to initialize the object.
Giving a class a private constructor gives you fine control over construction. Figure 1 shows how the MFC library does this. If a class, CWnd in this case, contains a static instance of itself, you have a compile-time guarantee that there will be only one CWnd object (see Figure 1). The instance itself is public, but its construction is private.
Figure 1 Giving a Class a Private Constructor
GUIMGR.H
class CWnd
{
private:
CWnd(HWND hWndSpecial);// private constructor to initialize a
// special window object
public:
static CWnd desktopWindow;// the only CWnd object
};
GUIMGR.CPP
o
o
o
CWnd CWnd::desktopWindow((HWND)0);
A private constructor is usually the best solution, but sometimes you need a less restrictive solution. For example, if your program must be able to create the class object anywhere or at any time during execution, you can define a public function that creates the object if it does not exist or returns the object if it does (see Figure 2). This method doesn’t guarantee that only one object will be created, but you’ll know when a second object is erroneously created.
Figure 2 Creating a Single Class Object
GUIMGR.H
class GUIManager
{
private:
CWnd(HWND hWndSpecial);// private constructor to initialize
// a special window object
public:
CWnd* CreateDesktop()
{
if (desktopWindow = = NULL)
desktopWindow = new CWnd((HWND)0);
return desktopWindow;
}
};
GUIMGR.CPP
#include "guimgr.h"
// don't forget to define the object
CWnd* CWnd::desktopWindow = NULL;
QI just got Microsoft C/C++ version 7 and I’ve been looking at some of the sample MFC Windows programs. I noticed that none of them have a WinMain. Where do MFC apps start?
AThe MFC library provides a globally defined WinMain function that initializes and runs MFC’s CWinApp class object. CWinApp has six member functions that you can override (see Figure 3).
Figure 3 Six Member Functions of CWinApp
CWinApp Function | Purpose | When to Override |
InitApplication | One-time application initialization | Seldom |
InitInstance | Creates main window | Almost always |
Run | Message loop | Seldom |
PreTranslateMessage | Extra message processing | Seldom |
OnIdle | Idle loop processing | If needed to perform background tasks when no messages are pending |
ExitInstance | Called when program terminates, it returns exit code to Windows | Sometimes, to perform application cleanup |
You need to derive your own class from CWinApp. Mine is called CTheApp (see BLANKWIN.H in Figure 4).
Figure 4 BLANKWIN
BLANKWIN.H
class CMainWindow : public CFrameWnd
{
public:
CMainWindow(); // Constructor
o
o // Other handler functions
o
};
class CTheApp: public CWinApp
{
public:
virtual BOOL InitInstance();
};
BLANKWIN.CPP
CMainWindow::CMainWindow()
{
LoadAccelTable("MainAccelTable");
Create(NULL,"Blank Screen", WS_OVERLAPPEDWINDOW, rectDefault,
NULL, "MainMenu");
}
BOOL CTheApp::InitInstance()
{
m_pMainWnd = new CMainWindow();
m_pMainWnd->ShowWindow(m_nCndShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
Your application class should override the InitInstance virtual member function so it can have its own behavior. The other functions shown in Figure 3 can also be overridden to obtain other kinds of behavior. Your InitInstance contains the code for instance-specific initialization. Each instance usually creates at least one visible main window.
InitInstance uses the "new" operator to initialize another class object, CMainWindow, derived from the MFC CFrameWnd class (see Figure 4). When the constructor for this object is invoked, it creates the data structures necessary to display a window on the screen. After the CMainWindow constructor returns, InitInstance calls two member functions of CMainWindow, ShowWindow and UpdateWindow, which are inherited from CFrameWnd, to make the window visible. The m_pMainWindow identifier, inherited from the CWinApp class, is used to store a pointer to the application’s main window object.
The final piece in the "where does an MFC application start" puzzle is the actual declaration of an application object. The following global declaration creates the application object:
CTheApp myApp;
The debugging version of the library will ASSERT if there is more than one CWinApp object. MFC application startup is different in style and structure from the startup of an application with a WinMain, because MFC is object-oriented. You’ll find the MFC approach much easier.
QI’m converting my existing Windows C code to C++. I’m reworking things so that I use class interfaces as much as possible. So that I can reuse code, I’m writing DLLs. How can I export a class interface as a DLL so that others can derive classes from my class library?
AThis is a very difficult question with no easy answer. C++ compilers for Windows each offer their own solutions to the problem of placing a class in a DLL. Unfortunately many problems arise with code maintenance, space efficiency, and speed. These solutions all use the _export keyword to denote a class that will reside in a DLL. The compiler then generates all the needed prologue and epilogue code to call functions in the DLL. This requires your class interfaces and associated data to be large model, which has many known deficiencies.
When writing DLLs for C, most people are careful only to export a small set of APIs. This makes it easier to maintain in the long run. If you export every single C++ member function, the level of maintenance required becomes unmanageable and defeats the purpose of using a DLL in the first place.
Why not restrict your exported code to small set of C interfaces that delegate to a C++ class? This has a number of advantages. First, your class library implementation can be used by non-C++ programmers. Second, the interface is easier to manage and maintain over time. Third, clients of your library are not restricted to large model.
QI understand that C++ compilers do lots of work for me (behind my back!), generating functions I don’t need and making my programs bigger and slower. Is that true?
AWell, C++ doesn’t generate functions that you don’t need. It will generate functions that it needs. Most of the generated functions are by default very small and inlined. You should not rely on compiler generated functions. If your class does not supply implementations of the functions in Figure 5, C++ will generate default ones for you.
The compiler will also generate default functions for address-of (&) operations, but overriding these is so rare that I won’t talk about them here. These types of functions are inlined by most implementations, so there is little run time cost. You can also prevent the compiler from generating these functions by declaring private members in a base class (in the case of the copy initializer and assignment operator) or by declaring a constructor that takes arguments.
Figure 5 Functions Generated by Default
default constructor - FOO() { }
default destructor - ~FOO() { }// only generated if FOO has a base class
// with a destructor
copy initializer - FOO(const FOO&) // member-wise copy of member variables
assignment operator - FOO& operator = (const FOO&)
// member-wise copy of member variables