Programming C++ with Panache
A C++ Style Manual
David B. Sher
sherd@sunynassau.edu
Mat/Sta/CMP Dept., Nassau Community College
Garden City NY 11530

Style or panache is one of the ways programmers separate the professionals from the incompetents. Good style in your programs will improve your job prospects. Also good style in programs makes bugs easier to find and remove. Often using good style allows you to avoid bugs like mismatched ()’s in the first place. Stylish programs will seem slower to write but actually they will be faster since the style will force you to understand what you are doing.

A program with panache is entirely obvious. Each word of the program should have an obvious meaning to the programmer and the reader of the program. The reason and necessity (or at least convenience) of each element of the program should be obvious. Style should make every word of the program so necessary that a reader could not even think of another way of writing the same program. Of course, this is an unachievable ideal but an attempt at an ideal leads to greatness.

In this document I will include two programs called poor.cpp and styled.cpp. poor.cpp is a program in the worst style that I could make myself write. styled.cpp has the best style I could easily write. The program with poor style compiles with only 1 minor warning but does not work and the bugs are quite subtle. The good style program worked perfectly the first time I tried it.

A program with panache will not cause any warnings (or of course) any errors when compiled. Originally usersNumber was an int which was not my intention. A warning from the compiler will often indicate a poorly thought out decision in your code. Always think about the code that is warned and only change it when you understand why the code was confusing and whether your clarification results in correct code.

  1. Beautiful Indentation
    1. Keeping Lines on a Line

    2. Indenting and alignment is required if the program will work. I’ve seen many a student program and when the indenting is wrong they don’t work, usually they don’t compile. The first rule is that the lines of a program should be on a single line (if they fit):
      Wrong
      const int m = M; float 

      x[m]; int p = 2;

      Right
      float sum = 0; // sum of the numbers in the circularArray for calculating average

      float sumSquares = 0; // the sum of the squares of the #'s in circularArray for calculating variance

      There is no reason for the type float to be on the same line as the const m and on a different line from x[m]. The exceptions to this rule are:
      Lines that are too long can be broken if:
      You break them so that it is clear that they are incomplete.
      Wrong
      ResultVariable = ExoticFunction(x,MunicipalResult(y)
      + NuclearFactor(z);
      Right
      ResultVariable = ExoticFunction(x,MunicipalResult(y) +
      NuclearFactor(z);
      You indent the rest of the line to indicate that it is part of the previous line.
      Wrong
      ResultVariable = ExoticFunction(x,MunicipalResult(y) +
      NuclearFactor(z);
      Right
      ResultVariable = ExoticFunction(x,MunicipalResult(y) +
      NuclearFactor(z);
      The type of a function or an initialized variable can be separated from the declaration. An example of this type of coding is:

      // This is the storage for the circular array initialized to all 0's
      static float
      circularArray[circularArraySize] = { 0.0, 0.0, 0.0, 0.0, 0.0 };
      // This function calculates the next position in the circular array
      inline int
      next(int pos /* the position to update */)

    3. Lining up {}’s and other ()’s

    4. Every {,(, or [ must be aligned with the corresponding },), or ] either horizontally or vertically. Examples of horizontal alignment are sin(x), array[index], or
      for(index=0; index < size; index++) { sum += array[index]; cout << array[index] << endl;}

      Vertical alignment is used for larger or more complex expressions like:
      for

        (
        index=0, activeValuesStart = 3;
        index < size;
        index++, activeValuesStart += 2
        )
      {
        sum += array[index];
        cout << array[index] << endl;
      }

      Avoid code where the brackets don’t line up, even if you were copying your code from a professional source like a text book or the help documents with your compiler. A common version of unaligned brackets is:
      void DoesNothing(void *PointerToNothing){ // this function does nothing with an unimportant pointer
      }

    5. Indenting Blocks

    6. One of the first signs of code that doesn’t work is when the code inside a function or loop is not indented. The body of a loop must be indented from the loop to show what lines are in the loop. The body of a function is also indicated through indenting. If a long line is broken it should be indented too. The lines inside an if statement or switch statement should also be indented. The case statements will not be indented but the lines in each case should be. Lines within a block must be aligned. Some examples of correct and wrong code are shown below.
      1. Functions
      2. Wrong
        int obscure(int y) {
        cout << "Doing something obscure to " << y << endl;
        y /= y*y+1;¬should be indented
        if(y>5)return (y+1)/2;
        if(y<5) return y;¬should have same indent
        return 0;
        }
        Right
        int obscure(int y) {
        cout << "Doing something obscure to " << y << endl;
        y /= y*y+1;
        if(y>5)return (y+1)/2;
        if(y<5) return y;
        return 0;
        }
      3. Loops
      4. Wrong
        do {
        cin >> x[i // I really like this line ¬should be indented
        ]; s = s + x[ /* this line adds s to x[i] and puts the result in s */
        i]; s -= x[(i+m-1)%m]
        ss += x[i]*x[i], ss
        = ss - x[(i-1+m)%m]*x[i+m-1-m*((i+m-1)/m)];
        cout << "Avg is: " << s/m ¬ should be indented
        << "\tStdev is: " << ss/m -(s/m)*(s/m) << endl;
        } while(ss);
        Right
        do {
        cerr << "Enter New Number: ";
        cin >> usersNumber; // read number from user
        sum -= circularArray[position]; // remove effect of current # on sum

        cout << "After entering " << usersNumber << " the average is " <<
        sum / circularArraySize;
        // the variance is the average of the squares - the square of the average
        cout << " the variance is " << (sumSquares/circularArraySize - square(sum/circularArraySize)) << endl;
        } while (sumSquares > 0 ); // true only when array is cleared
        Right
        for(iterator=0;iterator<size;iterator++) // print and increment all elements of array { cout << array[iterator] << endl; 
        array[iterator]++;
        }
      5. Switch
      6. Wrong
        switch(userInput[0])
        {
        case ‘y’:
        case ‘Y’: // this is a positive answer
        return true;
        case ‘n’: ¬should not be indented
        case ‘N’: // this is a negative response
        globalvariable++; ¬should be indented
        return false; ¬should be indented
        default:
        cerr << "I did not understand: " << userInput << endl;
        }
        Right
        switch(userInput[0])
        {
        case ‘y’:
        case ‘Y’: // this is a positive answer
        return true;
        case ‘n’:
        case ‘N’: // this is a negative response
        globalvariable++;
        return false;
        default: cerr << "I did not understand: " << userInput << endl;
        break;
        }
      7. Types

      8.  
        Wrong
        // here we define a typical class it will describe a student

        class student 
        {

        protected: // this will contain the data. 
         
        char first[256],last[256],mi; // name inforation
        public: // this defines what the user can get at

        // constructors
         
        // first version everything supplied 
        student(char *f, char *l, char m,long id) 
        { init((const char *) f, (const char *) l, m, id ) ; }
        // returns the first name as a constant string
        const char *first_name() const 

        { return first; } 

        … 
        };
        Right
        // here we define a typical class it will describe a student

        class student {

        protected: // this will contain the data.  char first[256],last[256],mi; // name inforation
        public: // this defines what the user can get at // constructors
        // first version everything supplied 
        student(char *f, char *l, char m,long id) 
        { init((const char *) f, (const char *) l, m, id ) ; }

        // returns the first name as a constant string
        const char *first_name() const 
        { return first; } 
        …  };

  2. Clear Comments

  3. Comments tell you what a program is about, the code tells you what it does. Comments are about why a line is in your code. Only obscure or confusing code require a comment to tell you what the code does. For example:

    // the % makes the array circulate back to 0 when pos is circularArraySize -1
    return (pos + 1)%circularArraySize;

    is an example of obscure code that needs to be explained. Usually code is clearer than the above and what it does needs no further explanation.

    In general you should not wait till you have completed the code before you comment it. Writing a comment on code makes you stop and think about what you are doing. The thinking allows you to avoid bugs before you write them so the coding would go faster. Also if you want to show your code to the instructor or to a friend, having comments will mean that you don’t have to explain each line.

    1. Initial Comments

    2. Every file should begin with a large comment (delimited with /* and */) that discusses the purpose of the code in that file. If there are multiple files in the program it should discuss how this file relates to other files in the program. It should name the author of the program, often with a copyright notice. If this is the file containing the main function then the initial comment should discuss the behavior of the program. An example of a good initial comment is:

      /*
      This program is meant to demonstrate how a well written program can
      make a complex idea clear.
      Copyright David B. Sher 1998
      */

    3. Commenting Variables

    4. When introducing (declaring) a variable into a program it is necessary to explain why you need this variable. If you need more than one line to describe a variable then your variable is doing too much. A variable should have a simple easily explained purpose. You should explain this purpose when you write the variable declaration. If you can not write a simple comment explaining the variable then you need to think more before you write more code. Global variable are particularly important to comment since they are used throughout the program.

      An example of variable commenting is:

      Wrong
      const int m = M; float ¬every variable and constant should have its own line
      x[m]; int p = 2; ¬every variable should have a comment
      Right
      float sum = 0; // sum of the numbers in the circularArray for calculating average
      float sumSquares = 0; // the sum of the squares of the #'s in circularArray for calculating variance
      int position = 2; // starting position in circular array
    5. Commenting Types

    6. Types such as classes and types defined through typedefs are even more fundamental and vital than global variables. They control the meaning and even the syntax of the program. Several hours before writing a program should be invested in considering the types you are using. The comment must explain the purpose of the type in the program.

      Often a type is used to implement a metaphor. A type may implement a queue, which is a metaphor for a storage area where the first element that was entered is the first to be extracted, like a line for a bank teller. Such a type should have a comment that describes the metaphor for example:

      /* a storage area where the first element that was entered is the first to be extracted,
         like a line for a bank teller
         only the queue functions should be used to modify or extract data from this type
      */
      typedef struct
      { float storage[max]; // storage area for the floats
        int first,last; // indices of the first and late elements of the queue
        int size; // the number of elements in the queue
      } Queue;

      If you are commenting a complex type you should comment each part and express what each part does for the type. Explain how to use the type in the program Also discuss any caveats (limitations) on using the type. In the above example the last line of the code is a caveat.

    7. Commenting Functions

    8. Every function should have a comment that explains what a function does and how it uses its parameters. Of course, the comments must make sense unlike:

      // rubber baby bunker mice
      int main()

      Beyond that minimal level a comment should be sufficient so that a stranger can call your function without having to read the code, just the comment. Focus on the purpose of the function rather than how it works, for example:

      // This function squares a number to help calculate variance
      inline float square(float number)  { return number*number; }
       
       

    9. Commenting Loops

    10. Loops are used in programs either to traverse or search a data structure (like an array) or to interact with the user, usually. If you are traversing or searching a data structure you must explain which data structure is being searched or traversed. You must also explain why you are doing this. If it is a user interaction loop a shorter comment is usually sufficient since such loops tend to be self explanatory. If the loop is used for a more exotic purpose you should give a more extensive explanation since such a use of a loop will be surprising for experienced programmers.

      Some examples of comments on loops are:

      // Copies first N elements from array1 to array2
      for(index=0;index<N;index++) array1[index]=array2[index];

      // copies elements from linked list into an array
      for(linkPointer = list->next, index=0 ; linkPointer != NULL ; linkPointer=linkPoint->next,index++)
               array[index]=linkPointer->data;

    11. Commenting Conditionals

    12. Conditionals are if and switch statements. Often the actual condition in an if statement does not perfectly clarify why you are testing the conditional. For example:

      if(list->next != null) // then the list has no elements to act on
             doSomething;
      else // there are elements in the list

      It is usually necessary to examine each case in a switch statement and what it means. This comes up particularly often when parsing input:

      // determine the command from the first letter in the word
      switch(command[0])
      {
      case ‘a’:
      case ‘A’: // the activate commandActivate();
      break;
      default: // can’t figure out which command you want to execute cerr << "I do not understand " << command << endl;}

    13. Commenting Lines

    14. Many other lines require comments to clarify them. Any line that is not a simple print statement or equally obvious should have a comment explaining why the statement is necessary in the program. If you don’t know why then you are programming too quickly. Unless the line is quite unclear you need only express why the line is there and not what it does.
      Wrong
      Array[x] /= 2; ¬this line needs a comment

      cout << "Lets boogie\n" // prints a line which says lets boogie ¬this line doesn’t

      x++; // this line adds 1 to x ¬this comment doesn’t explain why

      Right
      size++; // after inserting the element the size of the list will be 1 larger

      cout << "Copyright David B. Sher 1998"

  4. Names of Distinction

  5. Functions, variables and types need names that (in order of importance):
    1. indicate the purpose of the item,
    2. are easily remembered,
    3. and are unique.
    If a local variable is meant to index the array you can call it index. If there are several array parameters to the function – inputArray, outputArray - for example, you can use indexInputArray as a variable name. Variables names can contain several words which are separated by capitalization or by _’s (user_name). More than two words in a variable name can make it too difficult to remember. Avoid words that are difficult to spell, like receive. There is a custom of using one letter variable names for indices, like array[i]. Such names are common but should be avoided unless you are implementing mathematical formulai. Similarly unless you are referring to a position on the X coordinate avoid using the name x for a variable.

    The general rule is: variable names should be words or word combinations containing ordinary vowels and consonants. This allows you to talk about your program with professors, bosses, and colleagues. It is difficult to discuss how the variable mxtplztz changes during each iteration of a loop.

    Avoid:
    void deleteElement(list input, int pos)
    { float pos; // this is a because the parameter pos can not be used in the program now.
    Giving any variable the same name as a type. For example: float float; struct link link;
    Giving a local variable the same name as a parameter (will cause ’s for sure). For example:
    Giving a local variable the same name as a global variable.
    Giving two local variables the same name unless they do exactly the same thing.

  6. Lost at C

  7. C, C++ and Java were designed to be programmed by experts, code written in these languages could not avoid being obscure.  Many features of this language were designed to encourage efficiencies no longer important in modern machines and programs.  A good programmer can avoid using obsolete and confusing language features.  Some language features offer advantages the designers did not imagine.  You show panache by taking advantage of features that improve and avoiding the rest.
    1. Punctuation Difficulties – ?:,&|

    2. There are several obscure uses of punctuation marks in C that can confuse novice or distracted programmers.  In particular, the , should be avoided except as a parameter separator.   Other legitimate uses for ,’s are separating declarations and using inside a for loop where ;’s are not allowed.
       
      Wrong  if(x>3,x<7) …  // pointless test of x against 3
      Right max(x,y)   // , separates parameters
      Right  for(int first=0,last=size-1; first<size; first+=1,last-=1)
      Another obscure piece of C syntax is the ?: combination. This is used as an if statement when you can not use the if statement.  Avoid using this syntax since it is obscure and often confusing.
      The single & and | punctuation marks are used for bit manipulation.  Unless you are masking bits avoid using this syntax.  If you don’t know what masking bits are then you shouldn’t use these operators.  Also >> and << should only be applied between integers when you actually are doing bit manipulations. Avoid using >> and << for integer multiplication and division.  Usually you can rely on your compiler’s optimizer for such simple tricks.
    3. ++ -- Madness Madness

    4. Whenever ++ or -- appear in a program it causes a side effect which can result in unexpected program behavior.  For example temp=array[index++]; mysteriously  changes the value of index while it also accesses an array.   Combining many operations on one line might be a fun game but the pain of unscrambling the confusing code that can result is simply not worth it.  Most of what we use ++ for can be done as easily with +=1.  Hence, temp=array[index++]; is the same as temp=array[index]; index+=1; but less obvious; and we must all strive to be obvious!
    5. The Magic of #

    6. C, and C++ inherit a remarkable facility called the macro preprocessor.  It runs before the compiler and implements include files /**/ comments.   C++ makes many of these commands unnecessary.  For example, do not use the #define command to define constants instead use the C++ const keyword.  Templates or overloading can be used instead of the macros built with a #define command.
      Include files are still useful and should be used for important data types and conditional compilation to handle inconsistencies between machines and compilers.  Also, header files should start with the sentinel lines:
      #ifndef TYPE_H
      #define TYPE_H
      and end with
      #endif
    7. Pointers and References

    8. Use reference parameters rather than pointer parameters unless a pointer is necessary. Avoid pointing to any local variables only point to variables allocated on the heap or static variables.  Always initialize pointer variables; uninitialized pointer variables can lead to very difficult variables.
       
  8. Organized Programs

  9. Clear programming requires organization of the code. In particular people have limited attention spans and can best focus on small items. C++ allows us to break up complex ideas into smaller structures. Focus on how one can make the items in your program smaller and simpler. Avoid or break up functions that take more than 15 lines to write.

    Important data structures like lists or trees should have their own header files and code files. Many programs can share the same data structures if they have their own files. Also interface code can often be shared among many programs if it is in separate files. Whenever you design a part of your program consider if you will probably use it in other programs. If so create a separate header and file for it.

    Functions that do several tasks should call functions that do simple tasks. Often simple functions can be used in many parts of the program. Some functions should be dedicated to the user interface or interfacing with the file system. There should be a main function that calls these when necessary. The other functions should avoid any file system or user interaction. This allows these functions to be used many different user interfaces. Even error messages can be handled with throws.

    If you organize your program this way you will have a more organized program and you will be able to reuse your carefully written code.

  10. Writing C++ with Class

  11. Classes in C++ allow you to design types for advanced data structures.   It gives you to features to make these types operate almost like built in types.  This power can result in beautifully clear code or incredibly confusing code.  If you design well your classes will be clear and easy to use.
    1. Private, Protected and Public

    2. All data members (members that are not functions) should be protected. Avoid private members since that makes derived members behave differently than members of the type.  Member functions that can only be called from members should also be protected.
    3. Operators

    4. Only define arithmetic operators (+*-/) for numeric classes (like complex).  Define index operators for collection classes.  Define a char* operator for every type so it is easy to print your types and put your type in error messages (by throwing).   If you want to be able to sort your data define ordering operators (< > == != <= >=).  If you define a copy constructor (to pass the type by value) also define an assignment operator.   Avoid the other types of operators since they can only be confusing.
    5. To Inline or not to Inline

    6. Use inline only for simple function definitions (no more than a few lines).
    7. Members vs. Functions

    8. Member functions should be used for modifications or accesses of a type that will be will be used a large number of places in a program or by a large number of programs that include the type.  If you want to do something complex that you will not need to do often write a function in a file.
  12. Panache

  13. No one is perfect. No one writes perfectly clear programs. Even the example above has flaws. If you are a perfectionist, think twice about writing a program, they are intolerably imperfect. Do not let this stop you from writing the best program you can. Do not let poor examples that come with your compiler or your text book dismay you. Writing with panache will result in programs that are fast to write and easy to read. You will be able to use repeatedly reuse your code and save a lot of time.


CopyRight David B. Sher 1996 Disclaimer

Number of visitors: 4098

Images from David B. Sher and also from