By David Wiley
C++ for Programmer Pete___________________________________________________ 1
1. Preface_____________________________________________________________ 4
1.1. Purpose__________________________________________________________________ 4
1.2. Information source________________________________________________________ 4
1.3. Contact information_______________________________________________________ 4
1.4. Who’s Pete?______________________________________________________________ 5
2. General Naming Guidelines____________________________________________ 6
2.1. Use entire words or syllables_________________________________________________ 6
2.2. Use mixed case____________________________________________________________ 6
2.3. Use the correct plural_______________________________________________________ 6
2.4. Begin all class and struct names with C___________________________________ 7
2.4.1. Listing – CVector_______________________________________________________ 7
2.4.2. Listing – vector________________________________________________________ 7
2.5. Prefix all class member variables with m_______________________________________ 7
2.5.1. Listing – Demonstration of m_ need__________________________________________ 8
2.5.2. Listing – Demonstration of m_ use___________________________________________ 8
2.6. Do not use _ to prefix any identifier names____________________________________ 8
2.7. Use Simplified Hungarian Notation for
variable names__________________________ 8
2.7.1. Table – Simplified Hungarian Notation________________________________________ 9
2.7.2. Table – Combinations_____________________________________________________ 9
2.7.3. Table – Sample user defined type variable names_________________________________ 9
3. Header Files________________________________________________________ 10
3.1. Listing – Header file code template__________________________________________ 10
4. Class Interface______________________________________________________ 11
4.1. Data hiding______________________________________________________________ 11
4.2. Virtual destructor_________________________________________________________ 11
4.3. Order by access level______________________________________________________ 11
4.4. GetState(), SetState(), and IsProperty()_______________________________________ 12
4.4.1. Listing – Access methods_________________________________________________ 12
5. Parameter Passing___________________________________________________ 13
5.1. Listing – Parameter passing examples________________________________________ 13
5.2. What does a pointer imply?________________________________________________ 14
5.2.1. Listing – Harmless function call____________________________________________ 14
5.2.2. Listing – Potentially harmful function call_____________________________________ 14
5.2.3. Listing – Evil function____________________________________________________ 14
6. Using const_______________________________________________________ 15
6.1. What does const mean when it’s on the right-hand side of a
class member function declaration?___________________________________________________________________ 15
6.1.1. Listing – Before pre-processor_____________________________________________ 15
6.1.2. Listing – After pre-processor______________________________________________ 15
6.2. It is about as powerful as a mall cop_________________________________________ 15
6.3. Use const instead of #define____________________________________________ 16
6.3.1. Listing – Using #define_________________________________________________ 16
6.3.2. Listing – Using const___________________________________________________ 16
7. Templates__________________________________________________________ 17
7.1. Listing – Template need___________________________________________________ 17
7.2. Listing – Template use_____________________________________________________ 17
7.3. Use reasonable names for your template
parameter variables____________________ 17
7.4. Use all capital letters on parameter names____________________________________ 17
7.5. Postfix _TYPE onto the end of parameter names______________________________ 17
7.6. Use typedef to make using your template classes easier
to use_________________ 18
7.6.1. Listing – typedef your templates__________________________________________ 18
7.7. Provide easy access to your class template
parameters__________________________ 18
7.7.1. Listing – Easy access to your template parameters_______________________________ 18
8. Enumerations_______________________________________________________ 19
8.1. Listing – Class with enumeration____________________________________________ 19
8.2. Listing – Enumeration as a bit mask_________________________________________ 19
8.3. Listing – Class versioning__________________________________________________ 20
9. Class Member Declaration List________________________________________ 21
9.1. Listing – Over use of declaration list_________________________________________ 21
10. Reference Counting__________________________________________________ 22
10.1. Listing – Proper reference counting________________________________________ 22
11. Multiple Constructors in a Class_______________________________________ 23
11.1. Listing – Multiple constructors____________________________________________ 23
12. Don’t Be Too Hasty__________________________________________________ 24
12.1. Listing – Destroy the virtual function table__________________________________ 24
12.2. Listing – Don’t destroy the virtual function
table____________________________ 24
13. Namespaces________________________________________________________ 25
13.1. Listing – In dire need of namespaces_______________________________________ 25
13.2. Listing – Uses namespace properly_________________________________________ 25
14. Tips and Tricks_____________________________________________________ 26
14.1. Pointers_______________________________________________________________ 26
14.1.1. Listing
– Helpful memory deletion templates__________________________________ 26
14.2. Copy constructor and assignment operator
relationship…_____________________ 26
14.2.1. Listing
- Merging the copy constructor and assignment operator____________________ 27
14.3. Passing structs as parameters_____________________________________________ 27
14.3.1. Listing
– In need of parameter simplification___________________________________ 28
14.3.2. Listing
– Much better____________________________________________________ 28
14.4. Equality expression evaluation: (1 == n), (1
<= n), etc.________________________ 29
14.5. Versioning_____________________________________________________________ 29
14.5.1. Listing
– Class versioning_________________________________________________ 29
14.6. Line grouping and commenting___________________________________________ 29
14.6.1. Listing
– Line grouping___________________________________________________ 30
15. Discussion Points____________________________________________________ 31
15.1. Error handling__________________________________________________________ 31
15.1.1. Exceptions____________________________________________________________ 31
15.1.2. Method
success and failure________________________________________________ 31
15.2. Curly brace placement___________________________________________________ 31
Every programmer inherently learns and practices a custom programming style. The reason for this is no doubt rooted in how programmers learn to program: a snippet from this book, a line from that, an algorithm from this magazine, an array class from that. Every programmer is essentially a melting pot for the many different styles that exist (I will leave as an exercise to the statistician readers the task of determining just how many combinations are possible and in what frequency). Having a custom style is generally suitable as long as the programmer abstains from interacting with other programmers; a prisoner to his (and increasingly, her) style.
Aside from the usual social discontinuities, problems surface when programmers begin to mingle. A random sample of C++ source code from the Internet will yield a variety of C++ dialects. Either you will learn some new things or your eyes will tire from poorly written code. The one constant is that you will never find two programmers that do things exactly the same way.
Even more problems occur when teams of programmers must work together. In this environment source code can make round trips through programmers, changing ever so slightly in each iteration. Small scale battles can occur in these code bytes in the form of moving curly braces and parenthesis around, adding or removing spaces, tabbing this, carriage-returning that, commenting this, not commenting at all, renaming variables, or using for loops instead of while loops. We end up fighting essentially irrelevant battles and wasting time (though at that time it is very, very important).
If everybody wrote code in the exact same way, then everything would be fine. This is obviously not the case and it is not the intention of this document to force you into writing code the way I write code. The purpose of this document is to address common problems and to provide solutions in a well-defined manner (with hope that you will end up writing code exactly like I do).
A lot of the information that I present in this document is based on Microsoft standards found in their Microsoft Development Network (MSDN) library. Much of the information I draw from Microsoft comes from one document: Microsoft Foundation Library Development Guidelines. Like it or not, Microsoft has been at least slightly successful in the software business, thus I argue that they are worth listening to. The rest is based on my own ten or so years of C/C++ programming. Three or so of the years are “professional” wherein I produced commercial software for the Microsoft Windows environments.
By no means do I consider myself to be even close to the ultimate authority on C++ programming. Though I do believe I have some good ideas and have picked up some good coding methods that I can consolidate into this document.
I always enjoy debating programming issues and styles and welcome them warmly. I often find this is a very good way to learn new things about C++ and software development. I welcome feedback of (almost) any type about this document or C++ and software development issues. If you have any comments on or additions to anything in this document please let me know and I will address them as best I can:
wiley@cs.ucdavis.edu.
Programmer Pete, for the purpose of this document, is the manifestation of all the other programmers in the world including the next generation of programmers. Pete also includes yourself sometime in the future because quite often it is difficult to reuse code that you wrote in the past. Had the Y2K programmers considered Pete in their development they would not have been so infamous.
The idea is to develop software for Programmer Pete to use. This way you won’t just be programming for yourself, but for the rest of the world.

Programmer Pete
Problem: Everybody has a different method for creating names for variables, classes, structs, methods, etc. The worst naming algorithm I have encountered is that of a beginning programmer who, when a new name was needed, would randomly copy a word of text from the screen and would then repeatedly remove or add some characters until it compiled. Needless to say, though I do anyway, this is a big problem.
This type of convention may work fine for one-time-use ten line programs, but if you want to reuse the code by giving it to your friend Pete, Pete will have to scour it for hours and possibly even rewrite it.
Solution: Choose the most natural name. Quite often it is the first name that comes to mind. Do not be afraid to be too expressive in your name, it is often easier to remember a name that does not contain odd abbreviations or alterations. When Pete reads your code he will easily be able to figure out what the name is supposed to represent because he doesn’t have to crack your name encryption algorithm.
It is easier to remember complete words rather than some awkward abbreviation of a word such as Window: Wnd, Wn, Wind, etc.
|
Use |
Don’t Use |
|
Vector |
Vctr |
|
ListIterator |
LstIt |
Use mixed case rather than underscores to separate words in names. Always capitalize the first letter.
|
Use |
Don’t Use |
|
|
ListIterator |
List_Iterator, Listiterator, listiterator, LIST_ITERATOR, LISTITER; border-top:none;mso-border-top-alt:solid windowtext .5pt;padding:0in 5.4pt 0in 5.4pt'> BigVector |
bigVector, Big_Vector, bigvector |
Don’t invent plurals for collections of objects. Use the proper plural, this way it is more easily remembered.
|
Use |
Don’t Use |
|
Axes |
Axiss |
|
Windows |
WindowCollection |
The C denotes that the data type being used is a class or struct and helps distinguish between data types and variables:
class CVector
{
. . .
};
void Foo()
{
CVector v; // Obvious that CVector is a class based data type
CVector vector, Vector, vct; // Can also use a variety of variations on the
// word ‘vector’ without worry of a name conflict
}
Try not to do:
class vector
{
. . .
}
void foo()
{
vector v; // Can’t tell what ‘vector’ is without looking at
// the definition (is it a struct, enum, class,
// macro, etc.)
vector vector; // Error, name conflict
vector Vector; // Odd looking (to me at least)
}
The major advantage to prefixing class names with C is that you can then use the class name (with out the C) as a variable name. How often have you named a class one thing, say line, then when you create some objects you end up mangling line to be lin, aline, line1, Line, lne, etc. If you prefix your class name with C you can name your variables naturally, in the case of CLine: Line, LineA, LineB, etc.
Problem: Quite often you want to name your local variables in your class member methods the same or similar name as the class member variable.
I would be willing to bet that every C++ programmer has done the following at least once:
class CVector
{
public:
CVector(int x, int y)
{
x = x; // An error I’ve seen many times
y = y; // in beginning programming courses
}
private:
int x, y;
};
The goal is to choose a local variable name that closely relates to the member variable name.
Solution: Prefix m_ onto your member variable names. By doing this you can accomplish two things:
1) Improved local and member variable relationships
2) Improved differentiation between local and member variables
class CVector
{
public:
CVector(int x, int y)
{
m_x = x; // Obvious which parameter gets assigned to
m_y = y; // which member variable
}
private:
int m_x, m_y;
};
By using m_ you reduce confusion when writing code because the variable names are essentially the same, while at the same time you can easily distinguish between them.
The ANSI C specification allows identifiers that begin with two underscores or an underscore followed by a capital letter to be reserved for compiler use. To make things simpler for Pete when he tries to compile your code on the quantum computer of the future, do not begin any identifier with an underscore.
Problem: You find yourself in the middle of a thousand-line function staring at a variable name wondering what the type of the variable is. The variable happens to be declared on the first line of the function and you are viewing the file through a very slow telnet connection.
Solution: Scroll and wait. An alternate solution would have been to use Hungarian notation for variable naming (along with not writing a thousand-line function). A Hungarian Microsoft programmer developed the notation (hence the name). The idea is to prefix variable names with the type of the variable. Microsoft has simplified Hungarian Notation to become Simplified Hungarian Notation. I have modified this yet again to remove the Microsoft specific content and add some of my own:
|
Prefix |
Type |
Description |
Example |
|
n |
int |
any integer type |
nCount |
|
ch |
char |
any character type |
chLetter |
|
f |
float, double |
floating point |
fPercent |
|
b |
bool |
any boolean type |
bDone |
|
l |
long |
any long type |
lDistance |
|
p |
* |
any pointer |
pObject, pnCount |
|
sz |
* |
nul terminated string of characters |
szText |
|
pfn |
* |
function pointer |
pfnProgress |
|
h |
handle |
handle to something |
hMenu |
Variable names can thus be constructed from combinations of the above prefixes (at least the combinations that make sense).
|
Type |
Example |
|
int |
nCount |
|
int * |
pnCount |
|
bool * |
pbDone |
|
bool & |
bDone // Nothing special for references |
The reason this is called “Simplified” is because true Hungarian Notation is too cumbersome. It accounts for less than 32-bit operating systems wherein you are required to specify things like whether a pointer is far or near. In addition it also accounts for signing and bit length of types. I have also seen variations that prefix the return types onto function names. It is too cumbersome and they do not even define a prefix for floating point types (at least not that I could find). They must not use floats or doubles (probably not a bad Idea on Intel machines).
A paradox arises while using Hungarian Notation of any variation. What do you do with user defined types? You don’t want to invent your own prefix because it may not make sense to Pete, but by not prefixing a variable with something you are not helping the situation either. I believe the best solution is to name your variables in such a way that you don’t need to use Hungarian Notation to decipher the variable’s type.
|
Type |
Example |
|
CPoint |
Point, PointA, pPoint |
|
CProjector |
Projector, pProjector |
Problem: In the day and age of hundreds and thousands of files in a project, quite often it becomes very difficult to remember who includes what header files. Without taking any precautions you will get many “already defined” errors.
Solution: To prevent these errors add the following code template to your header files:
#ifndef CLASSNAME_H_ // The prefix is derived from the class name in this file
#define CLASSNAME_H_ // Don’t prefix the identifier with an _
.
.
.
class CClassName
{
.
.
.
};
.
.
.
#endif // CLASSNAME_H_
This sequence of preprocessor commands allows this header file to be included more than once in any particular implementation or header file without generating “already defined” errors.
You should also try to have only one class per header and implementation file. This greatly simplifies the compilation process as well as partitioning your source code into logical divisions. For extremely large class implementations (over several thousand lines) it would be reasonable to divide the implementation into more than one file.
Class member data variables should be protected via private or protected in a class declaration. Since private access limits a variable’s usefulness so quickly, you should initially designate all class member variables as protected and only promote those variables that meet the following rule to private:
Private rule: Derived classes will rarely need access to the variable, and when they do it is always of read only nature.
Over use of private results in a well-protected class, though it can limit the derived classes’ speed and maximum level of performance. To improve access to private members, use inline methods to access them.
Implement your class’s destructor as virtual if your class will be derived from. This way proper destruction of derived objects will occur.
Order your class declaration top-down in the header file by access level: public, protected, and then private. When Pete decides that he wants to use your class he will browse of your class declaration looking for methods that he can perform on the object. By placing private members first Pete would be immediately confronted with members he cannot use; thus he would have to search further down in your class to find the members that he can use.
Do not have multiple sections of each access level; it is confusing to have to switch back and forth between access levels when trying to learn what a class can do.
For a given “property” of an object, there should be two access methods to access it. A GetProperty method that retrieves the value, and a SetProperty method that sets the value. The data member that actually stores this property should be hidden. You should use the methods instead of the data member wherever possible. The GetProperty method should be declared const. Get-state methods that return Boolean values can be of the form IsProperty rather than GetProperty.
class CExample
{
public:
CExample()
{
m_nValue = -1;
m_bIsSet = FALSE;
}
int GetValue()const
{
return m_nValue;
}
void SetValue(int nVal)
{
m_nValue = nVal;
m_bIsSet = TRUE;
}
BOOL IsValueSet()const
{
return m_bIsSet;
}
void SetIsValueSet(BOOL bSet)
{
m_bIsSet = bSet;
}
protected:
int m_nValue;
BOOL m_bIsSet;
};
The appropriate use of pointers and references can simplify code, enhance readability, and greatly improve performance.
Parameter passing
guidelines:
1. If the data being passed is a primitive type and/or its size in bytes is small (less than the size of a pointer to that type), then it is acceptable to pass the parameter by value, otherwise…
2. Pass the parameter as a const reference if the data will not be changed within the function, otherwise…
3. Pass the parameter as a non-const pointer if the data will be changed inside the function.
Exceptions:
1. If the name of the function implies a change to the passed object, it is acceptable to pass the parameter as a non-const reference (i.e. GetNext(…), GetText(…))
2. If the parameters of the function where the function is frequently called are primarily pointers, then it is acceptable to pass parameters as non-const or const pointers (i.e. in the Microsoft world within OnPaint() where the CDC objects are almost always pointers: pDC)
The following is a demonstration of various parameter passing methods:
typedef struct tagCMyStruct
{
char szBuf1[5000];
char szBuf2[5000];
char szBuf3[5000];
}CMyStruct;
void PrintMyStructOne(CMyStruct ms)
{
printf(“%s,%s,%s”, ms.szBuf1, ms.szBuf2, ms.szBuf3);
}
void PrintMyStructTwo(const CMyStruct *pms)
{
printf(“%s,%s,%s”,pms->szBuf1, pms->szBuf2, pms->szBuf3);
}
void PrintMyStructThree(const CMyStruct &ms)
{
printf(“%s,%s,%s”, ms.szBuf1, ms.szBuf2, ms.szBuf3);
}
void main (void)
{
CMyStruct ms;
. . . // Fill ms with some data
PrintMyStructOne(ms);
PrintMyStructTwo(&ms);
PrintMyStructThree(ms);
}
PrintMyStructOne() , the naïve implementation, copies the CMyStruct data into its parameter ms, thus it copies 15,000 bytes of data. It is not desirable to be copying this much data when it is not necessary.
PrintMyStructTwo() does not copy data, but when used, as in main() above, you must pass it a pointer. Passing a pointer implies (to me at least) that the data object could very well be modified within the function that is being called. Even though the parameter is a const in the function declaration, it is not clear from the perspective of main() that the function being called does not modify the object.
The body of PrintMyStructTwo() is also slightly more complicated. Now you must deal with pointers and de-referencing within your function.
PrintMyStructThree() does not copy data, and because you do not pass it a pointer to the object, it doesn’t imply that the function will be changing the passed object. Note that the body of xxxThree() and the naïve method xxxOne() are the same. Also Note that the call in main() to xxxThree() looks exactly the same as the call to the naïve method xxxOne(). It is just as simple to use as the naïve method and looks cleaner than the pointer approach in xxxTwo().
Based on my own survey and experiences (and backed-up in some respect by Microsoft documentation):
1. If you pass an object pointer to a function, it is highly possible that the function changes the data pointed to.
2. If you simply pass the object to a function, it is unlikely that the object will be modified within the function.
Or at least I would like to assume the above…
void main(void)
{
double fA(1.0), fB(2.0), fC(3.0);
fA = sqrt(fB); // Pretty obvious that fB will NOT be changed within sqrt()
fC = sin(fA); // Pretty obvious that fA will NOT be changed within sin()
}
void main(void)
{
MyStruct ms;
SomeFunction(ms); // Does this function modify the passed object?
// It is impossible to tell directly from the
// function name.
}
The survey above leads me to a function declared in the following manner:
void SomeFunction(MyStruct &ms)
{
strcpy(ms.pBuf1, ”Junk”);
}
This is simply evil to construct a function in this manner. It is not implied from the calling perspective of the function (other than the name of the function) that the object passed will be modified and in this case the name does not imply anything.
An exception is something like GetNext(int &nPos), where the name of the function hints to the action taken on the object. In this case it is acceptable (but not recommended) to use a non-const reference.
Appropriate usage of the const modifier enforces data encapsulation and protection. It is conventionally ranked in importance about as high as a mall cop, but it has far more power than a mall cop when used appropriately. Use the const modifier liberally and whenever possible. In some cases liberal use of const will bring to light some serious bugs.
|
Use |
Don’t Use |
|
void Foo(const CMyStruct &ms); |
void Foo(CMyStruct ms); |
|
int GetProp() const; |
int GetProp(); |
|
const char* GetName() const; |
CString GetName(); |
|
const CMyStruct* GetStruct() const; |
CMyStruct* GetStruct(); |
If you want to allow direct access to a member variable via a function call, make two versions of the function:
1. MyStruct& GetStruct();
2. const MyStruct& GetStruct() const;
This will allow both types of access at the appropriate time such as on the left or right side of an assignment operator.
Technically, it defines the function to be a constant function. In as simple terms as I can explain, when C++ gets converted to C (C++ is simply a pre-processor for C you may recall), the const on the right side gets placed in front of the this pointer.
So the function declarations:
class CMyClass
{
.
.
.
int GetValueOne();
int GetValueTwo() const;
};
Get converted into something like:
int GetValueOne(CMyClass *this);
int GetValueTwo(const CMyClass *this);
Thus, inside a const function the this pointer is declared as const so the calling object cannot be changed.
If you want to you can always cast a constant object to a non-constant reference and then do whatever you want to it. This is why const is a mall cop and not a marine.
You should use const instead of #define to declare constant global values so that only one copy of the object exists.
#define BUFSIZE 1024
#define SENTENCE “this is a sentence”
void main (void)
{
char szText[BUFSIZE];
strcpy(szText, SENTENCE);
.
.
.
if(strcmp(szText, SENTENCE) != 0)
strcpy(szText, SENTENCE);
.
.
.
}
In the listing above character string for SENTENCE is repeated several times. Thus, the storage for this is repeated several times. A better way is as follows:
const int nBufferSize = 1024;
const char szSentence[] = “this is a sentence”;
void main (void)
{
char szText[nBufferSize];
strcpy(szText, szSentence);
.
.
.
if(strcmp(szText, szSentence) != 0)
strcpy(szText, szSentence);
.
.
.
}
Now there is only one copy of the data.
Use templates whenever possible. As soon as you identify that you are customizing a class or method based on the type, convert it to a template. If you make more than one copy of a method because of a varying type, then you have made too many copies. Think one, there can be only one! With this philosophy you will develop much more versatile code.
int swap(int a, int b)
{
int t = a;
a = b;
b = t;
}
float swap(float a, float b)
{
float t = a;
a = b;
b = t;
}
.
.
.
Should be changed to:
template <class TYPE>
TYPE swap(TYPE a, TYPE b)
{
TYPE temp = a;
a = b;
b = temp;
}
Enough with T as being the type variable – use something more descriptive. If you are expecting some sort of vector call your type variable VECTOR, if you are expecting an array call it ARRAY. T, T1, and T2, are not descriptive enough.
All capitals distinguish the name from the other names in your code. You can then easily identify which names are template parameters and those that are not.
If the template parameter is a data type, postfix _TYPE onto the name to more easily identify it as a data type parameter.
If you have a template class that expects twenty parameters than it is a pain to write functions that use this class. Using typedef will make it easier to use:
template <class BASECLASS, class VAR, class CHEESE, class VECTOR, int COUNT>
class CTemplateClass : public BASECLASS
{
.
.
.
};
typedef CTemplateClass<CMyClass, int, float, CMyVector, 5> CMyNewType;
void Print(const CMyNewType &Obj)
{
.
.
.
}
Provide easy access to your template parameters in by using typedef and static functions:
template <class BASECLASS, class VAR, class CHEESE, class VECTOR, int COUNT>
class CTemplateClass : public BASECLASS
{
public:
typedef BASECLASS CBaseClass;
typedef VAR CVarType;
typedef CHEESE CCheeseType;
typedef VECTOR CVectorType;
static int GetCount() {return COUNT;}
typedef CTemplateClass<BASECLASS,
VAR,
CHEESE,
VECTOR,
COUNT> CThisClass;
.
.
.
CTemplateClass(const CThisClass &); // Much easier to write than
// the alternative
};
This will clean up your code and improve readability significantly. Just image how long the declaration of an overloaded operator for the class above would be without typedefs.
I believe enumerations to be the long lost rich uncle that every programmer forgets about. They are a goldmine of simplification.
Enumeration guidelines:
1) Use enumerations instead of #define.
2) Encapsulate the enumeration inside a class if you can. This way it can be identified as being closely related to that particular class. This will also prevent a rogue enumeration from appearing in a header file all by itself so that you and Pete have to search many files to find out where it is used.
3) typedef your enumerations to make them more usable.
Prefix enumeration variable names with enum. This way, when the variable is used you know exactly what it is.
class CMyClass
{
public:
typedef enum
{
}ERROR;
.
.
.
};
Use enumerations for bit masks:
typedef enum
{
enumSuccess = 0x00,
enumError1 = 0x01,
enumError2 = 0x02,
enumError3 = 0x04,
enumError4 = 0x08,
enumError5 = 0x10
}BITMASK;