C++ for Programmer Pete

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

 


1.   Preface

1.1.        Purpose

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).

1.2.        Information source

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.

1.3.        Contact information

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.

 


1.4.        Who’s Pete?

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

2.   General Naming Guidelines

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.

 

2.1.        Use entire words or syllables

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

 

2.2.        Use mixed case

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

 

2.3.        Use the correct plural

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

 


2.4.        Begin all class and struct names with C

The C denotes that the data type being used is a class or struct and helps distinguish between data types and variables:

 

2.4.1.            Listing – CVector

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:

2.4.2.            Listing – vector

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.

 

2.5.        Prefix all class member variables with m_

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:

2.5.1.            Listing – Demonstration of m_ need

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

2.5.2.            Listing – Demonstration of m_ use

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.

2.6.        Do not use _ to prefix any identifier names

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.

2.7.        Use Simplified Hungarian Notation for variable names

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:

2.7.1.            Table – Simplified Hungarian Notation

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).

2.7.2.            Table – Combinations

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.

2.7.3.            Table – Sample user defined type variable names

Type

Example

CPoint

Point, PointA, pPoint

CProjector

Projector, pProjector

 


3.   Header Files

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:

3.1.        Listing – Header file code template

#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.

 


4.   Class Interface

4.1.        Data hiding

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.

 

4.2.        Virtual destructor

Implement your class’s destructor as virtual if your class will be derived from.  This way proper destruction of derived objects will occur.

 

4.3.        Order by access level

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.

4.4.        GetState(), SetState(), and IsProperty()

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.

 

4.4.1.            Listing – Access methods

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;

};

 


5.   Parameter Passing

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:

5.1.        Listing – Parameter passing examples

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().

 

5.2.        What does a pointer imply?

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…

5.2.1.            Listing – Harmless function call

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()

}

 

5.2.2.            Listing – Potentially harmful function call

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:

5.2.3.            Listing – Evil function

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.


6.   Using const

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.

6.1.        What does const mean when it’s on the right-hand side of a class member function declaration?

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:

6.1.1.            Listing – Before pre-processor

class CMyClass

{

.

.

.

int GetValueOne();

        int GetValueTwo() const;

};

 

Get converted into something like:

6.1.2.            Listing – After pre-processor

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.

6.2.        It is about as powerful as a mall cop

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.

 

6.3.        Use const instead of #define

You should use const instead of #define to declare constant global values so that only one copy of the object exists.

6.3.1.            Listing – Using #define

#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:

6.3.2.            Listing – Using const

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.

 

 

 


7.   Templates

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.

7.1.        Listing – Template need

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:

7.2.        Listing – Template use

template <class TYPE>

TYPE swap(TYPE a, TYPE b)

{

TYPE temp = a;

a = b;

b = temp;

}

 

7.3.        Use reasonable names for your template parameter variables

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.

 

7.4.        Use all capital letters on parameter names

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.

 

7.5.        Postfix _TYPE onto the end of parameter names

If the template parameter is a data type, postfix _TYPE onto the name to more easily identify it as a data type parameter.

7.6.        Use typedef to make using your template classes easier to use

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:

7.6.1.            Listing – typedef your templates

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)

{

.

.

.

}

 

7.7.        Provide easy access to your class template parameters

Provide easy access to your template parameters in by using typedef and static functions:

7.7.1.            Listing – Easy access to your template parameters

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.


8.   Enumerations

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.

8.1.        Listing – Class with enumeration

class CMyClass

{

public:

        typedef enum

        {

        }ERROR;

        .

        .

        .

};

 

Use enumerations for bit masks:

8.2.        Listing – Enumeration as a bit mask

typedef enum

{

        enumSuccess            = 0x00,

        enumError1             = 0x01,

        enumError2             = 0x02,

        enumError3             = 0x04,

        enumError4             = 0x08,

        enumError5             = 0x10

}BITMASK;

 

Even for keeping track of a class version:

8.3.        Listing – Class versioning

class CMyClass

{

public:

        typedef enum

{

               enumVersion1           = 0x0100,

               enumVersion2           = 0x0101,

               enumVersion3           = 0x0200,

               enumCurrentVersion     = enumVersion3 // Yup, two vars can have

// the same value

}VERSION;

.

.

.

};

 

 

See the Tips and Tricks section for more information on class versioning.


9.   Class Member Declaration List

It just troubles me when I see this:

9.1.        Listing – Over use of declaration list

class CMyClass

{

public:

        CMyClass() :   a(0), b(0), c(0), d(0), e(0), f(0),

g(0.0f), h(0.0f), i(0.0f), j(0.0f), k(0.0f)

{}

.

.

.

protected:

int a, b, c, d, e, f;

float g, h, i, j, k;

};

 

Is the body of the constructor such a sacred place that normal variable declarations cannot be placed in there?  Good grief, just move them into the constructor.  Just put the things you need to in the declaration list.

 


10.        Reference Counting

Include the copy constructor when you need to count object instantiations.  The compiler makes a default copy constructor if you do not specify one, and this one has no idea that you are trying to count object instantiations.

10.1.    Listing – Proper reference counting

class CMyClass

{

public:

        CMyClass()                    {m_nRef++;. . .}

        CMyClass(int n)               {m_nRef++;. . .}

        CMyClass(const CMyClass &mc)  {m_nRef++;. . .}

        virtual CMyClass()            {m_nRef--;. . .}

        .

.

.

protected:

        static int m_nRef;

};

 

 


11.        Multiple Constructors in a Class

When you have multiple constructors in a class, write a single method called Construct() that initializes all of your member variables.  Then in each constructor, call Construct() on the first line.

11.1.    Listing – Multiple constructors

class CMyClass

{

public:

        CMyClass()

        {

Construct();

        }

        CMyClass(int n)

        {

Construct();

a = n;

        }

        CMyClass(int n, int k)

        {

Construct();

               a = n;

               b = k;

        }

        CMyClass(const CMyClass &Obj)

        {

Construct();

*this = Obj;

        }

 

protected:

        void Construct()

        {

               a = 0;

               b = 1;

               c = 2;

               d = 3;

               e = 4;

        }

       

        int a, b, c, d, e;

};

 

This way if you add new member variables, you only need to update one function rather than going to each constructor and figuring out what to do.  This greatly reduces the chance of forgetting a constructor or a member variable.


12.        Don’t Be Too Hasty

It may seem like a good idea to do something like:

12.1.    Listing – Destroy the virtual function table

class CMyClass : public CBaseClass

{

public:

        CMyClass()

        {

               memset(this, 0, sizeof(CMyClass));

        }

        .

        .

        .

};

 

In the simplest case this will work fine and achieve what you expect (initialize all variables to 0).  Though, if a virtual function is declared anywhere in the class hierarchy then you would be doing something very bad.  The call to memset() will overwrite the virtual function table for the object because sizeof() includes the virtual function table in its calculation.  The result is all kinds of strange problems.

 

If you really want to do something like this, something safer would be:

12.2.    Listing – Don’t destroy the virtual function table

class CMyClass : public CBaseClass

{

public:

        CMyClass()

        {

               memset(&m_pStart, 0, &m_pEnd – &m_pStart);

        }

 

protected:

        unsigned char m_Start;

        .       // Include variable declarations here

        .       // that you want to clear

        .

        unsigned char m_End;

};

 

This way all the variables that are declared between the start and end markers are set 0.  This is still kind of hokey though.  You probably shouldn’t do it, but it is worth noting so that you do it safely.

 

13.        Namespaces

Do not prefix your class, method, or variable names with a concocted abbreviation that uniquely identifies your code.  Use name spaces to make your classes and functions unique.

 

The following possibility needs to use a namespace:

13.1.    Listing – In dire need of namespaces

extern int mylibValue;

extern int mylibCount;

 

class mylibVector

{

        . . .

};

 

float mylibMagnitude(const mylibVector &v);

float mylibDotProduct(const mylibVector &va, const mylibVector &vb);

 

Should be changed to:

13.2.    Listing – Uses namespace properly

namespace MyLib

{

 

extern int nValue;

extern int nCount;

 

class CVector

{

        . . .

};

 

float Magnitude(const CVector &v);

float DotProduct(const CVector &va, const CVector &vb);

 

}; // namespace MyLib

 

To use the namespace you can either place “using namespace MyLib” at the top of each file you use the namespace’s members in or use the namespace selectively: “MyLib::Magnitude(v)” very similar to accessing a public static member function in a class.

 

The use of namespace clarify code and reduce a lot of extra typing. 


14.        Tips and Tricks

14.1.    Pointers

Here’s some helpful templates for handling memory deletion:

 

14.1.1.        Listing – Helpful memory deletion templates

template <class TYPE>

inline void DELETE_POINTER(TYPE * &pointer)

{

        delete pointer;

        pointer = NULL;

}

 

template <class TYPE>

inline void DELETE_ARRAY(TYPE * &pointer)

{

        delete [] pointer;

        pointer = NULL;

}

 

Using the above templates does the following important things:

1)     Sets the pointer to NULL upon deletion.  This prevents stale pointer variables.

2)     Makes the choice between “array” and “normal” memory deletion more obvious.  It is too easy to forget the [] for an array.  Having to type either “ARRAY” or “POINTER” makes you think about the decision a bit more.

 

I have found memory leaks in my own programs when converting old programs that use plain old delete to use these templates instead.

 

14.2.    Copy constructor and assignment operator relationship…

Since these two methods are essentially the same (aside from how they are invoked) you can simplify the copy constructor declaration by initializing the data members and then calling the assignment operator directly.  This will simplify adding new member variables and eliminate the chance of the two methods getting out of synchronization.

 

14.2.1.        Listing - Merging the copy constructor and assignment operator

class CMyClass

{

public:

        CMyClass()

{

Construct();

}

CMyClass(const CMyClass &obj)

{

        Construct();   // Initialize members

        *this = obj;   // Call assignment operator directly

}

 

CMyClass& operator= (const CMyClass &obj)

{

        m_nA = obj.m_nA;

        m_nB = obj.m_nB;

}

 

protected:

        void Construct()

{

               m_nA = m_nB = 0;

}

 

        int m_nA, m_nB;

};

 

14.3.    Passing structs as parameters

There are two cases that benefit greatly from having a structs passed as the parameter rather than its individual parameter counterpart:

1)     When passing a large number of parameters to any method.  When parameter list become long, it becomes more difficult to remember the order of the parameters and even the parameters themselves.  Passing a single struct object relieves this headache.

2)     If function call order is important, make a single function that wraps the sequentially called methods and takes a single struct as the parameter.  This eliminates the need for Pete to know that the methods need to be called in a particular order and makes it easier to pass the parameters.

 

The following listing shows some methods in serious need of parameter passing help:

14.3.1.        Listing – In need of parameter simplification

class CMyClass

{

public:

        CMyClass()

{

}

 

        void One(int nA, int nB, int nC, int nD, CMyClass *pObj)

        {

                pObj->m_nVal   = nA * nB* nC;

               m_nVal         = nD;

        }

        void Two()

        {

               m_nVal *= 4;

               pObj->m_nVal   = m_nVal * nE * nF * nG;

        }

protected;

        int m_nVal;

};

 

Should be changed to:

14.3.2.        Listing – Much better

class CMyClass

{

public:

        CMyClass(){}

 

        typedef struct

{

               int nA, nB, nC, nD, nE, nF, nG;

        CMyClass *pObj;

}CData;

 

        void Method(CData *pData)

{

        One(pData);

        Two();

        Three(pData);

}

 

protected;

        void One(CData *pData)

        {

               pData ->pObj->m_nVal   = (pData ->nA) * (pData ->nB) * (pData ->nC);

               m_nVal                 = pData ->nD;

        }

        void Two()

        {

               m_nVal *= 4;

        }

        void Three(CData *pData)

        {

               pData ->pObj->m_nVal   =       m_nVal *

(pData ->nE) *

(pData ->nF) *

(pData ->nG);

        }

 

        int m_nVal;

};

 

 

14.4.    Equality expression evaluation: (1 == n), (1 <= n), etc.

When evaluating equality expressions, try to place the constant parameter (if there is one) on the left-hand side.  This will reduce the chances of accidentally forgetting an equal (or less-than) sign and thus assigning a variable incorrectly.  If the parameter on the left-hand side is constant, the compiler will catch to error if you do make an assignment mistake.

 

14.5.    Versioning

Class versioning can be handled elegantly by adding an enumeration to the class.

14.5.1.        Listing – Class versioning

class CMyClass

{

public:

        typedef enum

{

               enumVersion1           = 0x0100,

enumVersion2           = 0x0101,

        enumVersion3           = 0x0200,

        enumCurrentVersion     = enumVersion3

}VERSION;

.

.

.

void Save(ostream &os, const VERSION nVersion)

{

               os << nVersion;               // Save the version number

 

        os << m_nA;            // Version 1

 

        if(nVersion >= enumVersion2)

                       os << m_nB;    // Version 2

       

        if(nVersion >= enumVersion3)

                       os << m_nC;    // Version 3

 

        return os;

}

void Load(istream &is)

{

               int nVersion;

 

        is >> nVersion;               // Read version

 

        is >> m_nA;            // Version 1

 

        if(nVersion >= enumVersion2)

                       is >> m_nB;    // Version 2

 

        if(nVersion >= enumVersion3)

                       is >> m_nC;    // Version 3

}

 

protected:

        int m_nA, m_nB, m_nC;

};

 

14.6.    Line grouping and commenting

Try to group lines of code in small groups (less-than 20 lines per group) and identify the groups with comments to aid in readability.  Separate groups by at least one blank line.

 

Here is a piece of code from a program of mine:

14.6.1.        Listing – Line grouping

void CFieldDefinition::DrawField(CDC *pDC)

{

        // Local variable definitions

        CPen    PenNormal(PS_SOLID, 0, RGB(144, 144, 144));

        CPen    PenRemove(PS_SOLID, 0, RGB(144, 144, 0));

        CPen    PenMultiplePass(PS_SOLID, 0, RGB(0,0,225));

        CPen    PenInflection(PS_SOLID, 0, RGB(192,0,0));

        CPen    PenRed(PS_SOLID, 0, RGB(192,0,0));

        CBrush  BrushRed(RGB(192, 0, 0));

        CPt     *pPt = NULL;

        CFont   Font;

 

        // Save the DC state

        pDC->SaveDC();

 

        // Setup the font

        Font.CreatePointFont(70, "Arial", pDC);

        pDC->SelectObject(&Font);

        pDC->SetTextColor(RGB(0,0,0));

        pDC->SetBkMode(TRANSPARENT);

        pDC->SetTextAlign(TA_CENTER | TA_BASELINE);

        .

        .

        .

};

 

Even though you have no idea what the function is supposed to do, you can generally figure out what it is doing just by quickly scanning the line groups and comments.  You can quickly scan the comments to find the section of code that you want to find.  Once found, you can more closely examine the lines of code.

 

 

15.        Discussion Points

15.1.    Error handling

15.1.1.        Exceptions

Please email me any reason to use exceptions, unless it is one of my own exceptions listed here:

1)     To catch real exceptions caused by hardware or OS failure: power shutoff to something, network cable unplugged, out-of-memory, etc.

2)     To catch other people’s cop-out exceptions.

3)     To catch other people’s laziness exceptions because they didn’t want to handle an error condition.

4)     To catch other people’s “solutions” to “uncommon” situations.

 

Give me a break, programmers aren’t supposed to be this lazy.  You might learn something by trying to handle some of these “uncommon” situations.  There could be a very good reason for a method failure, figure it out.

 

Exceptions obscure code readability and require Pete to use exceptions as well to handle your errors.  Far too often I have seen code that uses exceptions as a cop-out to handle reasonable errors.  Over use of exceptions is a growing problem and I think programmers should be penalized for overuse.

15.1.2.        Method success and failure

A very reasonable method of handling method success and failures is to only have methods returning either void or BOOL types.  Methods returning void are assumed to work correctly every time.  Methods returning a BOOL type return TRUE on success and FALSE when an error occurs.

 

Microsoft has been very successful in using this type of error handling saving exceptions for things like database and file access as well as other OS failures (true “uncommon” non-deterministic cases).  Success of a method is tested very easily in an if statement and errors can be propagated easily to calling methods.

 

15.2.    Curly brace placement

I would estimate about 60-70% of people use curly braces the same way I do (see listings above for examples), whereas the rest of the people place the opening brace on the end of a line.  My argument for not placing braces at the end of lines is two-fold.  First, if the line scrolls off the screen, you can’t see the brace anymore.  Two, when scanning code to find blocks (defined by braces) it is easier and faster to just move your eyes vertically, rather that both vertically and horizontally to find them.

 

By placing braces at the beginning of their own lines it is much easier to decipher code blocks.  Comments can even be added immediately after the opening brace (on the same line) to hint to what that block is trying to accomplish.

 

 

 

dfwiley@ucdavis.edu    http://wwwcsif.cs.ucdavis.edu/~wiley/homepage.html

 

Copyright © 2000 David Wiley.  All rights reserved.