| Getting Started |
This topic provides coding guidelines when using C++. It also has useful information when debugging projects in Visual Studio.
Naming conventions
Type Names
Function Names
Argument names
True and false
Class Design
Class Layout
Public data
Class size
Inline methods
Comments
Construction
Initialization versus assignment in constructors
Assignment operators
Casting
const Methods
Using the const modifier
Type definitions and constants
Syntactic guidelines
Indentation
Implementation organization
Avoid macros
Comments
White space
Operators
Operator precedence
Nested if Statements
Function declarations
Global scope
Brackets
Variable declaration
Bit-Fields
Nested Headers
Switch Statements
Use references
Initialization
Null initialization
Exceptions
Avoid Global Data
Avoid macros
Using C++ with MFC and Win32
Standard C++ Data Types
Use ASSERT and VERIFY
Use WIN32_ASSERT
Character strings
Application Settings
Windows and MFC Function Calls
Localization Requirements
Use String Resources
Support Unicode
Smart Types
Direct-To-COM
Active Template Library
Useful C++ Tips
A Better Callback Model
Debugging Tips in Developer Studio
Backing up after failure
Unicode string display
Variable value display
Undocking windows
Conditional breakpoints
Preloading DLLs
Changing display formats
MFC Class Autoexpand
Keyboard Shortcuts
Navigating through online Help topics
Name variables and constants using the following format (this is an abridged Hungarian notation):
[<scope>_]<type><name>
| Prefix | Variable scope |
|---|---|
| m | Instance class members |
| c | Static class member (including constants) |
| g | Global static variable |
| <empty> | local variable or struct or public class member |
<type>
| Prefix | Data Type |
|---|---|
| b | Boolean |
| by | byte or unsigned char |
| cx/cy | short used as size |
| dw | DWORD, double word or unsigned long |
| fn | Function |
| h | Handle |
| i | int (integer) |
| l | Long |
| p | a pointer |
| s | String |
| sz | ASCIIZ null-terminated string |
| w | WORD unsigned int |
| x,y | short used as coordinates |
<name> describes how the variable is used, or what it contains.The <scope> and <type> portions should always be lowercase, and the <name> should use mixed case:
| Variable Name | Description |
|---|---|
| m_hWnd | a handle to a HWND |
| ipEnvelope | a smart pointer to a COM interface |
| m_pUnkOuter | a pointer to an object |
| c_isLoaded | a static class member |
| g_pWindowList | a global pointer to an object |
All type names (class, struct, enum, and typedef) begin with an uppercase letter and use mixed case for the rest of the name:
class Foo : public CObject { . . .};
struct Bar { . . .};
enum ShapeType { . . . };
typedef int* FooInt;
Typedefs for function pointers (callbacks) append Proc to the end of their names.
typedef void (*FooProgressProc)(int step);
Enumeration values all begin with a lowercase string that identifies the project; in the case of ArcObjects this is esri, and each string occurs on separate lines:
typedef enum esriQuuxness
{
esriQLow,
esriQMedium,
esriQHigh
} esriQuuxness;
Name functions using the following conventions:
For simple accessor and mutator functions, use Get<Property> and Set<Property>:
int GetSize();
void SetSize(int size);
If the client is providing storage for the result, use Query<Property>:
void QuerySize(int& size);
For state functions, use Set<State>, and Is<State> or Can<State>:
bool IsFileDirty();
void SetFileDirty(bool dirty);
bool CanConnect();
Where the semantics of an operation are obvious from the types of the arguments, leave type names out of the function names.
Instead of
AddDatabase(Database& db);
consider using
Add(Database& db);
Instead of
ConvertFoo2Bar(Foo* foo, Bar* bar);
consider using
Convert(Foo* foo, Bar* bar)
If a client relinquishes ownership of some data to an object, use Give<Property>. If an object relinquishes ownership of some data to a client, use Take<Property>:
void GiveGraphic(Graphic* graphic);
Graphic* TakeGraphic(int itemNum);
Use function overloading when a particular operation works with differ-ent argument types:
void Append(const CString& text);
void Append(int number);
Use descriptive argument names in function declarations. The argument name should clearly indicate what purpose the argument serves:
bool Send(int messageID, const char* address, const char* message);
There are at least three different sets of keywords for indicating the truth value of an expression. C++ has a built in data-type bool, with keywords true and false. Win32 defines TRUE and FALSE macros. VB compatible COM programming requires the use of the Automation type VARIANT_BOOL, with macros VARIANT_TRUE and VARIANT_FALSE.
These keyword macros have one thing in common. True evaluates to a nonzero value, and False evaluates to zero. VARIANT_TRUE is defined as -1, which means that the correct macros and keywords must be used when comparing variables.
Conforming to a class design standard leads to easy-to-use and maintainable C++ class implementations.
Organize class definitions in the following manner:
class MyClass : public CObject, private MyPrivateClass
{
// The public description of the class goes here. It describes what
// the class represents (from a client's perspective), and highlights
// which methods are the most important. Optionally, it shows examples
// of how to use the class.
public:
// Nested class and struct definitions.
// Enumerations, typedefs and constants.
MyClass() {}
virtual ~MyClass() {}
// Public operations.
// Public accessor/mutator functions.
protected:
// Protected description of the class goes here. This documentation
// usually consists of instructions to potential subclassers on how to
// subclass the class correctly.
// Nested class and struct definitions.
// Enumerations, typedefs and constants.
// Protected data members.
// Subclass-accessible operations. These are usually virtual.
private:
// Nested class definitions.
// Enumerations, typedefs, and constants.
// Private data members.
// Private operations.
};
Organizing classes this way helps clients of the class, since it groups public operations and functions at the beginning. All the proprietary implementation details occur at the end of the class definition, since clients do not need to know about them.
Do not make data members public unless the class is intended to be a semi-intelligent structure. One of the major benefits to using objects is the ability to encapsulate and hide implementation details from clients.
Keep classes small to decrease their complexity and increase their reusability. If you cannot summarize what the class does in a paragraph or less, chances are it is too complex and should be broken up into multiple classes.
Use inline methods only for empty implementations or for those containing only a few statements. Do not add a semicolon after the function body, but do add spaces to offset the brackets when there are statements in the function body. When several methods are inlined, line up the function bodies on the same column.
M Class() : m_count(0) {}
void SetCount(int count) { m_count = count; }
To increase class legibility, add comments after the method or data member. If the comment fits to the right, place it there; otherwise, add it afterwards as an indented comment.
public:
void SetCount(int count);
//
// Sets the count property of the object. Use this method only
// when you are resetting the object.
int GetCount(); // Gets the current count.
private:
int m_count; // The current count.
bool m_inited;
//
// This property indicates whether or not the object is
// currently being inited.
Be sure to provide a copy constructor and overload for operator if the default structure-wise copy will result in an invalid object. Alternatively, consider hiding both by making them private.
MyClass(const MyClass& rhs);
MyClass& operator=(const MyClass& rhs);
Initialization versus assignment in constructors
When the constructor is invoked for an instance of a class, the follow-ing operations occur in the following order (storage is allocated for the entire instance):
To avoid redundant operators, the following approach to constructor definition is suggested:
The assignment operators (=) should be explicitly defined for all classes. The automatic member-wise copy provided by the compiler is adequate only for shallow copy situations. Even if it works for initial development, it is likely to be inadequate when maintenance is performed. The following precautions should always be taken:
Type& Type::operator = (const Type& rhs)
{
if (this != &rhs)
{
... code to perform cop goes here ...
}
return *this;
}
DerivedType& DerivedType::operator = (const DerivedType& rhs)
{
if (this != &rhs)
{
BaseType::operator = (rhs);
... code to perform cop goes here ...
}
return *this;
}
In general, all casts should now use one of the following explicit castings.
static_cast<>()
const_caset<>()
dynamic_cast<>()
reinterpret_cast<>()
The new style casts are preferred because they are more explicit and more visible.
Make methods const when they do not change the object in any way.
int GetCount() const { return m_count; }
If a method is conceptually const from the client's viewpoint, but internally the implementation needs to adjust some private data member, make the function const but cast away its const-like quality in the implementation.
int GetCount() const;
int MyClass::GetCount() const
{
MyClass* self = const_cast<MyClass*> this; // Cast away const-ness
if (self->m_countLoaded)
self->LoadCount();
return m_count;
}
The keyword mutable can explicitly exempt data elements from const-like quality.
The const modifier is used in variable declaration to indicate that the variable cannot be modified after initialization. If the variable is declared with program, file, or function scope, it must be initialized when it is declared. When a pointer variable is declared, there are five possible options, as shown below.
| Statement | Meaning |
|---|---|
| Foo* pFoo | Both the pointer and the referenced data may be modified |
| const Foo* pFoo | The pointer may be modified, but not the referenced data |
| const Foo& pFoo | The referenced data may not be modified |
| Foo* const pFoo | The referenced data may be modified, but not the pointer |
| const Foo* const pFoo | Neither the pointer or referenced data may be modified |
const options for pointer variables
When using reference variables, the reference may never be modified. The const modifier only refers to the referenced data.
When const is used as a keyword following a class-member function, it indicates that the member function will not modify any class member variables. The const keyword must be used in both the interface definition and the implementation.
Type definitions and constants
If a constant or a type definition (class, struct, enum, or typedef) conceptually belongs to another class (that is, its only use is within the interface or implementation of another class), place it within the public, protected, or private scope of that class.
class Foo : public CObject
{
public:
struct Bar
{
int width;
int height;
};
typedef int ProgressLevel;
protected:
typedef enum esriProgress
{
esriPIdle,
esriPRunning,
esriPCompleted
} esriProgress;
};
The following syntactic guidelines make code more readable; they help maintainability and group development.
Use tabs for indentation, and set the tab size equal to two spaces. Do not replace tabs with spaces.
Organize .cpp files as follows:
// Include precompiled header.
// Other includes.
// Macro definitions.
// Global data.
// Static class members.
// Constructor(s).
// Destructor.
// Public operations.
// These should occur in the same order as the class definition.
// Protected operations.
// These should occur in the same order as the class definition.
// Private operations.
// These should occur in the same order as the class definition.
Where possible, use const definitions instead of macros.
Instead of
#define MAX_COUNT 10
use
const int g_maxCount = 10;
Instead of
#define DEFAULT_USER TEXT("Moe")
use
const TCHAR* g_defaultUser = TEXT("Moe");
Use C++-style comments rather than C comments, unless you are writing a file that needs to be compiled by the C compiler.
// This is a C++ comment and should be used in all C++ code.
/* This is a C comment and should onl be used in C code. */
Arguments should be separated by a comma and single space. Spaces should not occur between the function name and the initial parenthesis, or between the parentheses and the arguments.
result = MyFunction(count, name, &context);
Separate functions with at least one blank line.
void MyClass::MyFunction1()
{
}
void MyClass::MyFunction2()
{
}
Surround all operators with a space to the left and right.
size += sizeof(address);
i = j / 10 - 25;
Do not use extra spaces with these operators: !, #, ->, ., ++, and —.
if (!fileIsDirty) return;
#define DEBUG_ME
AfxGetApp()->ParseCommandLine(cmdInfo);
theConnection.Close();
if (i++ > 10 && j— < 100)
Where operator precedence is not immediately obvious, use parentheses to indicate order of execution.
result = (i - (10 - count)) / 42;
Avoid deeply nested blocks of if statements. They are difficult to read and debug.
if (i < 10)
{
if (i != 5)
{
if (j == 42)
{
MyFunc(i, j);
}
}
}
Instead, use algorithmically equivalent else-if blocks that check the reverse conditions and are not deeply nested:
if (i <= 0)
{
}
else if (i == 5)
{
}
else if (j == 42)
{
MyFunc(i, j);
}
Whenever possible, place function declarations on a single line:
bool ConnectToDatabase(const char* machineName, const char* databaseName);
If the declaration is too long, break it up into multiple indented lines, with all argument names positioned in the same column:
bool Connection::ConnectToDatabase(
const char* machineName,
const char* databaseName,
const char* userName,
const char* password,
unsigned long timeout,
int& connectionID);
When calling functions, try to place all arguments on a single line. If this is not possible, break them up into multiple lines, with each line indented one tab stop in from the leftmost character of the function name:
bool connectionResult = myConnection.ConnectToDatabase(machine, database,
user, password, timeout,
connectionID);
Use :: to indicate global scope.
result = ::AfxMessageBox(errMsg, MB_OK, 0);
Brackets should occupy an entire line by themselves.
for (int i = 0; i < 10; i++)
{
}
if (i <= 10)
{
}
else
{
}
Where possible, declare variables where they are used, rather than grouping them together at the beginning of a function.
void MyFunc()
{
. . .
CString database;
theDB.Quer DatabaseName(database);
. . .
}
Where possible, declare loop variables in the first line of a for statement.
for (int i = 0; i < 0; i++)
{
. . .
}
Avoid declaring multiple local variables on a single line.
int connCount, connSuccess, passwordHandle, securityAttributes;
Instead, put them on separate lines, or at least group together only those that are logically related.
int connCount, connSuccess;
int passwordHandle, securityAttributes;
When declaring pointers, place the asterisk directly next to the type, and leave a space before the variable, argument, or function name.
Instead of
char *myText;
use
char* myText;
Instead of
void *MyFunc(int *arg);
use
void* MyFunc(int* arg);
Use bit-fields where possible to promote efficiency.
unsigned m_flagA:1;
unsigned m_flagB:1;
unsigned:0; // pads to integer boundary
Avoid including headers in other headers. Use forward declarations where possible.
class Bar;
class Foo
{
public:
Bar* m_bar;
};
Construct a switch statement as follows. Note that the case and break keywords are indented one level, and the statements are all indented two levels.
switch (code)
{
case firstCase:
. . .
break;
case secondCase:
. . .
break;
default:
. . .
break;
}
When an individual case contains many statements, move them into a separate function, or enclose them with additional brackets.
case nthCase:
{
. . .
}
break;
Always provide a default case within switch statements, even if the result is to log an error message and terminate. Always provide a break or return statement for each case path, or an explicit comment on the justification for fall-through behavior.
Use references instead of pointers unless a NULL pointer value is needed. This is because the semantics of passing a pointer in C and C++ is very ambiguous.
void MyFunc(int* s);
The parameters of the function above could represent any of the following:
By using references (and const), these ambiguities are avoided.
void MyFunc(int& s);
void MyFunc(const int[]& s);
void MyFunc(const int& s);
void MyFunc(int& s);
Use initialization syntax to initialize all data members to their default values, unless the initialization is conditional. Do not leave any members uninitialized. Place each data member on its own separate line.
MyClass()
: m_count(0),
m_name(0)
{
}
Use 0 instead of NULL. In C++, the value 0 can be used to initialize any numeric or pointer variable.
A class should handle the exceptions thrown by objects that it uses, and should define and throw its own exceptions when an unrecoverable situation occurs.
Global data is inherently dangerous in a multithreaded environment. Where possible, try to embed all data in objects. In situations where data represents a shared resource, be sure to protect access to it with a critical section.
C++ provides language constructs that in many cases obviate the need for macros. The constructs are integrated into the compiler and debugger. Thus, you gain type safety and ease of debugging.
Instead of doing this:
#define MyConstant 10
do this:
const long s_MyConstant = 10
Instead of doing this:
#define MyHelper(a, b) \
a = 1; \
b = 2; \
a = b + a; \
do this:
inline double MyHelper(double a, double b) {…}
About the only time you need macros is to adjust behavior in accordance with build settings:
#ifdef _DEBUG
OutputDebugString("I'm Here");
#endif
Use standard C++ data types (int, short, long, bool, void, and others) unless the exact size of the data is critical to the behavior of the function, as with serialization or file I/O. In these cases, use Windows data types that are explicitly signed/unsigned and have an unambiguous size.
| Window Type | Description |
|---|---|
| BYTE | unsigned char |
| SHORT | signed 16-bit integer |
| LONG | signed 32-bit integer |
| WORD | unsigned 16-bit integer |
| DWORD | unsigned 32-bit integer |
Window data types
The ASSERT and VERIFY macros are invaluable debugging aids that should be used liberally throughout your code to check for entry and exit conditions, or any other exceptional situations.
ASSERT(pWnd);
VERIFY(loading && userCount > 2);
Use ASSERT during the development phase to insure that clients are adhering to rules for your interfaces. An assertion failure during development indicates that the contract between caller and callee has been broken.
The VERIFY macro does not go away in release builds. Use this only to check for catastrophic failure.
ASSERT and VERIFY behave identically in debug builds. However, in release builds, ASSERT compiles into nothing whereas the arguments to VERIFY get treated as regular statements.
ASSERT(wnd && loading); // NOP in release build.
VERIFY(contents->LoadContextMenu()); // LoadContextMenu happens in
// release build!
Any Win32 call that sets an error code can use WIN32_ASSERT to throw an exception that displays the result of GetLastError(). However, this macro behaves the same as VERIFY, in that the side effect remains even in a release build, so be sure that this is the behavior you want.
Consider using CString for all string data that you track and manipulate, instead of managing your own character arrays.
If MFC is not available, consider using one of the string smart types covered later in this topic.
Use the Windows registry to store and retrieve application settings. Do not use .INI files.
Windows and MFC Function Calls
Calls to all Windows and global MFC functions should use :: to indicate global scope.
When developing an application intended for use in more than one language, a number of issues must be considered that will make the localization of the software an easier process.
Never place string constants in the code; instead, define them in a re-source file from which they are loaded at runtime.
CString errorMessage;
errorMessage.LoadString(IDS_FILE_NOT_FOUND);
The only exceptions are debugging strings—they may reside directly in the code, since they do not affect the released product and need not be localized.
Store all string constants together in a standard module to facilitate translation to other languages.
All code should be Unicode-compliant; therefore, use arrays of TCHAR (instead of char), for representing character strings. Depending on the compilation settings, TCHAR expands either into single-character strings (ANSI) or wide-character strings (Unicode).
For string literals, use the TEXT macro to force the string or character to be Unicode compliant.
TCHAR dirSep = TEXT('\');
CString driveName(TEXT("C:"), 2);
Instead of the standard ANSI string functions, use the generic text map-ping macros. A list of the more common string-handling functions, along with the correct macro to use, appears on the table below.
| ANSI Function | Unicode-compliant macro |
|---|---|
| strlen | _tcslen |
| strcat | _tcscat |
| strncpy | _tcsncpy |
| strchr | _tcschr |
| strncmp | _tcsncmp |
| strstr | _tcsstr |
| atoi | _ttoi |
| atol | _ttol |
| splitpath | _tsplitpath |
For a complete list of generic text-mapping macros, refer to the Visual C++ online help, in C/C++ Run-Time Library Reference, in the 'Generic Text Mappings' chapter.
Smart types are objects that behave like types. They are C++ class implementations that encapsulate a data type, wrapping it with operators and functions that make working with the underlying type easier and less error prone, but transparent. When these smart types encapsulate an interface pointer, they are referred to as smart pointers. Smart pointers work by working with the IUnknown interface to ensure that resource allocation and deallocation is correctly managed. They accomplish this by various functions, construct and destruct methods, and overloaded operators. There are numerous smart types available to the C++ programmer. The two main types of smart types covered here are defined by Direct-To-COM and the Active Template Library. The relevant Direct-To- COM compiler extensions for the ArcObjects developer will be covered in the Active Template Library section later in this topic.
DTC was an initiative from Microsoft to make COM C++ programming more Visual Basic like. To achieve this DTC provides a set of classes and compiler extensions that shipped initially with Visual Studio 5.
Smart types can make the task of working with COM interfaces and data types easier, since many of the API calls are moved into a class implementation; however, they must be used with caution, and never without a clear understanding of how they are interacting with the encapsulated data type.
The smart type classes supplied with DTC are known as the Compiler COM Support Classes and consist of:
To define a smart pointer for an interface you can use the macro _COM_SMARTPTR_TYPEDEF like this:
_COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));
The compiler expands this as such:
typedef _com_ptr_t<_com_IIID<IFoo, __uuidof(IFoo)> > IFooPtr;
Once declared, it is simply a matter of declaring a variable as the type of the interface and appending Ptr to the end of the interface. Below are some common uses of this smart pointer that you will see in the numerous C++ samples.
// Get a CLSID GUID constant
extern "C" const GUID __declspec(selectan ) CLSID_Foo = \
{0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x00}};
// Declare Smart Pointers for IFoo, IBar and IGak interfaces
_COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo));
_COM_SMARTPTR_TYPEDEF(IBar, __uuidof(IBar));
_COM_SMARTPTR_TYPEDEF(IGak, __uuidof(IGak));
STDMETHODIMP SomeClass::Do ()
{
// Create Instance of Foo class and QI for IFoo interface
IFooPtr ipFoo(CLSID_Foo);
if (ipFoo == 0) return E_NOMEMORY
// Call method on IFoo to get IBar
IBarPtr ipBar;
HRESULT hr = ipFoo->get_Bar(&ipBar);
if (FAILED(hr)) return hr;
// QI IBar interface for IGak interface
IGakPtr ipGak(ipBar);
// Call method on IGak
hr = ipGak->DoSomething()
if (FAILED(hr)) return hr;
// Explicitly call Release()
ipGak = 0
ipBar = 0
// Let destructor call IFoo's Release
return S_OK;
}
ATL defines various smart types, as seen in the list below. You are free to combine both the ATL and DTC smart types in your code.
ATL smart types:
This section examines the first four smart types and their uses. The example code below written with ATL smart pointers, looks like the following:
// Get a CLSID GUID constant
extern "C" const GUID __declspec(selectan ) CLSID_Foo = \
{0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x00}};
STDMETHODIMP SomeClass::Do ()
{
// Create Instance of Foo class and QI for IFoo interface
CComPtr<IFoo> ipFoo;
HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_INPROC_SERVER,
IID_IFoo, (void **)& ipFoo);
if (FAILED(hr)) return hr
// Call method on IFoo to get IBar
CComPtr<IBar> ipBar;
HRESULT hr = ipFoo->get_Bar(&ipBar);
if (FAILED(hr)) return hr;
// IBar interface for IGak interface
CComQIPtr<IGak> ipGak(ipBar);
// Call method on IGak
hr = ipGak->DoSomething()
if (FAILED(hr)) return hr;
// Explicitly class Release()
ipGak = 0
ipBar = 0
// Let destructor call Foo's Release
return S_OK;
}
When reassigning an ATL smart pointer, a debug ASSERT is raised if the previous interface pointer is not explicitly released.
The most common smart pointer seen in the samples is the DTC type. In the examples below, which illustrate the BSTR and VARIANT data types, the DTC pointers are used. When working with CComBSTR, use the text mapping L"" to declare constant OLECHAR strings. CComVariant derives directly from the VARIANT data type, meaning that there is no overloading with its implementation, which in turn simplifies it use. It has a rich set of constructors and functions that make working with VARIANTs straightforward;+ there are even methods for reading and writing from streams. Be sure to call the Clear method before reusing the variable.
IFeaturePtr ipFeature(GetControllingUnknown()); // Get IFeature interface
// Get IFields interface and find index of Name field
long* lIndex;
IFieldsPtr ipFields;
HRESULT hr;
hr = ipFeature->get_Fields(&ipFields);
if (FAILED(hr)) return hr;
hr = ipFields->FindField(CComBSTR(L"Name"), &lIndex);
if (FAILED(hr)) return hr;
// Get OID change its t pe to String and set Name
// then set it back onto the feature
CComVariant vID;
hr = ipFeature->get_Value(0, &vID);
if FAILED(hr)) return hr;
// Change its data t pe
hr = vID.ChangeT pe(VT_BSTR);
if (FAILED(hr)) return hr;
hr = ipFeature->put_Value(lIndex, vID);
if (FAILED(hr)) return hr;
hr = ipFeature->Store();
if (FAILED(hr)) return hr;
When working with CComBSTR and CComVariant, the Detach() function returns the underlying data type and should be used when passing a pointer as an [out] parameter of a method. The use of the Detach method is shown below.
void GetName(BSTR* name)
{
CComBSTR bsName(L"FooBar");
*name = bsName.Detach();
}
These C++ tips are included here as tips for better development, and should not be seen as a set of rules.
Instead of passing function pointers and opaque context data to implement callbacks, consider defining an abstract notification class that encapsulates the various notification events that can be fired. Clients can then subclass and instantiate this class to register for notification.
To see how this might work, consider the following example. It shows how to implement a traditional callback mechanism between an object (Bar) and a client (Foo).
class Foo : public CObject
{
public:
Foo(Bar& bar) { bar.m_client = this; bar.m_clientProc = BarStub; }
int Bar(char* string) { printf("%s", string); }
static int BarStub(void* client, char* string)
{ ((Foo*)client) ->Bar(string); }
};
class Bar
{
public:
t pedef int (*BarProc)(void* client, char* string);
void* m_client;
BarProc m_clientProc;
void InvokeCallback()
{ if (m_clientProc) (*m_clientProc)(m_client, string); }
};
The Bar class defines the prototype for the callback function, and has two member variables: the address of the function to invoke and the object (stored as a void*) to pass along to the callback. Furthermore, at the Foo end, an additional static stub routine (BarStub) is needed that casts the opaque pointer to a Foo object before the real Foo method (Bar) is invoked. This seems like a lot of overhead for such a simple task. And it is dangerous, since it casts the void* into a Foo*.
However, there is a better way. By taking advantage of abstract classes in C++, the relationship between Foo and Bar can be much more cleanly implemented:
class Foo : public CObject, public BarInterface
{
public:
Foo(Bar& bar) { bar.m_client = this; }
int Bar(char* string) { printf("%s", string); }
};
class BarInterface
{
public:
virtual int Bar(void* client, char* string) = 0;
};
class Bar
{
public:
BarInterface* m_client;
void InvokeCallback() { if (m_client) m_client->Bar(string); }
};
The difference in this solution is that an abstract class, BarInterface, has been introduced. It lives alongside the Bar class, like before, and contains virtual methods that must be overridden by subclasses. These methods represent the events (callbacks) that the Bar class sends. The events are handled when a client provides a subclass that implements them. In this example, Foo derives both from CObject and from BarInterface, and implements the BarInterface method, Bar.
There are several advantages to this approach. First of all, type safety is always maintained, unlike the former example; objects are never cast to void* and then cast back to objects. Also, when a class provides multiple callbacks (which is often the case), they can all be encapsulated together in the abstract callback class. Some or all of them may be tagged with = 0, indicating that they must be overridden; this prevents clients from unwittingly implementing one callback while forgetting another which is vital for proper functioning. One can also provide default implementations for the callbacks, should a subclass choose not to implement one. (Providing defaulted functions under the traditional model is difficult and error-prone.) Lastly, by using virtual functions directly, there is no need for static stub functions.
Visual C++ comes with a feature-rich debugger. These tips will help you get the most from your debugging session.
When a function call has failed and you'd like to know why (by stepping into it), you don't have to restart the application. Use the Set Next Statement command to reposition the program cursor back to the statement that failed (right-click on the statement to bring up the debugging context menu). Then, just step-in to the function.
Set your debugger options to display Unicode strings (Tools -> Options… -> Debug -> Display Unicode Strings check box).
Pause the cursor over a variable name in the source code to see its current value. If it is a structure, click it and bring up the QuickWatch dialog box (the Eyeglasses icon, or Shift-F9), or drag and drop it into the Watch window.
If the Output window (or any docked window, for that matter) seems too small to you, try undocking it to make it a real window. Just right-click it and toggle the Docking View item.
Use conditional breakpoints when you need to stop at a breakpoint only once some condition is reached (a for-loop reaching a particular counter value). To do so, set the breakpoint normally, then bring up the Breakpoints window (Ctrl+B or Alt+F9). Select the specific breakpoint you just set and then click the Condition button to display a dialog in which you specify the breakpoint condition.
You can preload DLLs that you wish to debug before executing the program. This allows you to set breakpoints up front rather than wait until the DLL has been loaded during program execution. (Project -> Settings… -> Debug -> Category -> Additional DLLs.) Then, click in the list area below to add any DLLs you wish to have preloaded.
You can change the display format of variables in the QuickWatch dialog box or in the Watch window using the formatting symbols in the following table.
| Symbol | Format | Value | Displays |
|---|---|---|---|
| d, i | signed decimal integer | 0xF000F065 | -268373915 |
| u | unsigned decimal integer | 0x0065 | 101 |
| o | unsigned octal integer | 0xF065 | 0170145 |
| x, X | hexadecimal integer | 61541 | 0x0000F065 |
| l, h | long or short prefix for d, I, u, o, x, X | 00406042, hx | 0x0C22 |
| f | signed floating-point | 3./2. | 1.500000 |
| e | signed scientific notation | 3./2. | 1.500000e+00 |
| g | e or f, whichever is shorter | 3./2. | 1.5 |
| c | single character | 0x0065 | 'e' |
| s | string | 0x0012FDE8 | "Hello" |
| su | Unicode string | "Hello" | |
| hr | string | 0 | S_OK |
To use a formatting symbol, type the variable name followed by a comma and the appropriate symbol. For example, if var has a value of 0x0065, and you want to see the value in character form, type var,c in the Name column on the tab of the Watch window. When you press ENTER, the character-format value appears: var,c = 'e'. Likewise, assuming that hr is a variable holding HRESULTS, view a human-readable form of the HRESULT by typing "hr,hr" in the Name column.
You can use the formatting symbols shown in the following table to format the contents of memory locations.
| Symbol | Format | Value |
|---|---|---|
| ma | 64 ASCII characters | 0x0012ffac .4...0...".0W&.. .....1W&.0.:W..1 ...."..1.JO&.1.2 .."..1...0y....1 |
| m | 16 bytes in hex, followed by 16 ASCII characters | 0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4...0....".0W&.. |
| mb | 16 bytes in hex, followed by 16 ASCII characters | 0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4...0...".0W&.. |
| mw | 8 words | 0x0012ffac 34B3 00CB 3084 8094 22FF 308A 2657 0000 |
| md | 4 double-words | 0x0012ffac 00CB34B3 80943084 308A22FF 00002657 |
| mu | 2-byte characters (Unicode) | 0x0012fc60 8478 77f4 ffff ffff 0000 0000 0000 0000 |
With the memory location formatting symbols, you can type any value or expression that evaluates to a location. To display the value of a character array as a string, precede the array name with an ampersand, &yourname. A formatting character can also follow an expression:
To watch the value at an address or the value pointed to by a register, use the BY, WO, or DW operator:
Follow the operator with a variable, register, or constant. If the BY, WO, or DW operator is followed by a variable, then the environment watches the byte, word, or doubleword at the address contained in the variable.
You can also use the context operator { } to display the contents of any location.
You can apply formatting symbols to structures, arrays, pointers, and objects as unexpanded variables only. If you expand the variable, the specified formatting affects all members. You cannot apply formatting symbols to individual members.
To display a Unicode string in the Watch window or the QuickWatch dialog box, use the su format specifier. To display data bytes with Unicode characters in the Watch window or the QuickWatch dialog box, use the mu format specifier.
Microsoft Developer Studio has an autoexpand capability for Microsoft Foundation Class library classes. The string (or other information) between the braces ({ }) is automatically expanded.
There are numerous keyboard shortcuts that make working with the Visual Studio editor faster. Some of the more useful keyboard shortcuts are listed below.
The text editor uses many of the standard shortcut keys used by Windows applications like Word. Some specific source code editing short-cuts are listed below.
| Shortcut | Action |
|---|---|
| Alt+F8 | Correct indent selected code based on surrounding lines. |
| Ctrl+] | Find the matching brace. |
| Ctrl+J | Display list of members. |
| Ctrl+Spacebar | Complete the word, once the number of letters entered allows the editor to recognize it. Use full when completing function and variable names. |
| Tab | Indents selection one tab stop to the right. |
| Shift+Tab | Indents selection one tab to the left. |
Below is a table of common keyboard shortcuts used in the debugger.
| Shortcut | Action |
|---|---|
| F9 | Add or remove breakpoint from current line. |
| Ctrl+Shift+F9 | Remove all breakpoints. |
| Ctrl+F9 | Disable breakpoints. |
| Ctrl+Alt+A | Display auto window and move cursor into it. |
| Ctrl+Alt+C | Display call stack window and move cursor into it. |
| Ctrl+Alt+L | Display locals window and move cursor into it. |
| Ctrl+Alt+A | Display auto window and move cursor into it. |
| Shift+F5 | End debugging session. |
| F11 | Execute code one statement at a time, stepping into functions. |
| F10 | Execute code one statement at a time, stepping over functions. |
| Ctrl+Shift+F5 | Restart a debugging session. |
| Ctrl+F10 | Resume execution from current statement to selected statement. |
| F5 | Run the application. |
| Ctrl+F5 | Run the application without the debugger. |
| Ctrl+Shift+F10 | Set the next statement. |
| Ctrl+Break | Stop execution. |
Loading the following shortcuts can greatly increase your productivity with the Visual Studio development environment.
| Shortcut | Action |
|---|---|
| ESC | Close a menu or dialog box, cancel an operation in progress, or place focus in the current document window. |
| CTRL+SHIFT+N | Create a new file. |
| CTRL+N | Create a new project. |
| CTRL+F6 or CTRL+TAB | Cycle through the MDI child windows one window at a time. |
| CTRL+ALT+A | Display the auto window and move the cursor into it. |
| CTRL+ALT+C | Display the call stack window and move the cursor into it. |
| CTRL+ALT+T | Display the document outline window and move the cursor into it. |
| CTRL+H | Display the find window. |
| CTRL+F | Display the find window. If there is no current Find criteria, put the word under your cursor in the find box. |
| CTRL+ALT+I | Display the immediate window and move the cursor into it. Not available if you are in the text editor window. |
| CTRL+ALT+L | Display the locals window and move the cursor into it. |
| CTRL+ALT+O | Display the output window and move the cursor into it. |
| CTRL+ALT+J | Display the project explorer and move the cursor into it. |
| CTRL+ALT+P | Display the properties window and move the cursor into it. |
| CTRL+SHIFT+O | Open a file. |
| CTRL+O | Open a project. |
| CTRL+P | Print all or part of the document. |
| CTRL+SHIFT+S | Save all of the files, project, or documents. |
| CTRL+S | Select all. |
| CTRL+A | Save the current document or selected item or items. |
Navigating through online Help topics
Right-click a blank area of a toolbar to display a list of all the available toolbars. The Infoviewer toolbar contains up and down arrows that allow you to cycle through help topics in the order in which they appear in the table of contents. The left and right arrows cycle through help topics in the order that you visited them.