EECS 311: DATA STRUCTURES |
Home Course Info Links Grades |
Lectures Newsgroup Homework Exams |
This page was initially generated by Sven Rosvall's Coding Standard Generator, and then edited substantially to clean up the somewhat ancient HTML, and add some additional points.
You should also look at the miscellaneous EECS 311 C++ tips, focussing on items particularly useful in various assignments.
For a list of C and C++ style guide's, check out Chris Lott's site. One of the nicer ones is the Geotechnical Software Services style guide. Most of the rules there are included here.
Don't be mislead by the term "style." These guidelines are not secondary matters of fashion or personal taste. They are the primary elements for developing code that is maintainable and efficient. For example, there is little or no disagreement between the guidelines below and Pete Isensee's C++ Optimization Strategies and Techniques.
explicit
with single-parameter constructors to avoid accidental
implicit type conversions.
Do not use short cryptic names or names based on internal jokes. It should be easy to type a name without looking up how it is spelled.
Consider Roedy Green's essay on how not to name things as required reading for all programmers.
Exception: Loop variables and variables with a small scope (less than 20 lines) may have short names to save space if the purpose of that variable is obvious.
It is confusing when mixing languages for names. English is the preferred language because of its spread in the software market and because most libraries used already use English.
Scratch variables used for temporary storage or indices are best kept short. A programmer reading such variables should be able to assume that its value is not used outside a few lines of code. Common scratch variables for integers are i, j, k, m, n and for characters c and d.
This avoids name clashes.
This avoids name clashes.
The K&R Bracing Style was first introduced in The C Programming Language by Brian Kernighan and Dennis Ritchie.
The opening brace is placed at the end of the enclosing statement and the closing brace is on a line on its own lined up with the enclosing statement. Statements and declaration between the braces are indented relative to the enclosing statement.
Note that the opening brace of a function body is placed on a line on its own lined up with the function declaration.
Example:
void f(int a) { int i; if (a > 0) { i = a; } else { i = a; } }
Common indentation amounts are 2 or 4 columns. Use one amount consistently.
The code looks more consistent if all conditional and loop statements have braces. Even if there is only a single statement after the condition or loop statement today, there might be a need for more code in the future.
This also addresses a common concern that K&R, style makes it
hard to see if a long if
or for
has an opening brace. There should always an opening brace, and no need
to look for it.
The only time when two braces can occur on the same line is when they do not contain any code.
while (...) {}
There is no need to make code compact. Putting several statements on the same line only makes the code cryptic to read.
This makes it easier to see all variables. It also avoids the problem of knowing which variables are pointers.
int* p, i;
It is easy to forget that the star belongs to the declared name, not the type, and look at this and say that the type is "pointer to int" and both p and i are declared to this type.
Even if your editor handles long lines, other people may have set up their editors differently. Long lines in the code may also cause problems for other programs and printers.
Tabs make the source code difficult to read where different programs treat the tabs differently. The same code can look very differently in different editors and different machines.
Avoid using tabs in your source code to avoid this problem. Use spaces instead.
Be consistent and use the // ... style comments.
Being consistent on placement of comments removes any question on what the comment refers to.
Use a vector
not an array, use a string
not a const char *
. These containers are more flexible and less
error prone.
Write cout << "n = " << n << endl;
not printf("%d\n", n)
because the iostream library are more powerful and
can be extended to handle any data strucure, including
ones you define.
For example, #include <cmath>
not #include <math.h>
,
because the C++ versions work correctly with namespaces.
Don't write a for
or while
if it duplicates
what a templated function in <algorithm>
already does, e.g., copy
or
find
. The algorithm version is usually shorter, definitely
more communicative, and sometimes more efficient than what you would write.
Having as few declarations as possible in a header file reduces header dependencies.
The header file should have the same name as the class plus extension hpp.
External non-member functions that belong to the class interface may also be declared in the same header file.
On Windows machines, using different case in a file name than what it actually has won't matter, but your code won't work if compiled and run on another platform, such as Unix.
Code files, i.e., .cpp and .c files, should never be #include'd. It causes the included code to be recompiled, even when unchanged, and can lead to multiple definition errors.
Instead, code files should be made part of the project file (makefile), compiled separately as needed, and linked in.
If you have code that doesn't compile correctly unless you include a code file, it means your header file is missing something, such as a forward declaration or a templated member function.
The keyword inline should be used in both places. Using a separate inline file is useful to keep the header files clean and small. The separation is also useful where the inlining is disabled in debug builds. The inline file is then included from the source file instead of the header file to reduce compile time.
The include guard protects against the header file being included multiple times. Example:
#ifndef FILE_H #define FILE_H ... #endif
When a header is included, there should not be any need to include any other headers first. A simple way to make sure that a header file does not have any dependencies is to include it first in its corresponding implementation file. Example:
// implementation file for foobar.h #include "foobar.h" #include <stdio.h> ...
There is no requirement that a C++ compiler keep system headers in .h
files, only that #include <header-name>
will cause the right names
to be defined.
Having all #include directives in one place makes it easy to find them.
The directory structure may be different on other systems.
Parameter names document what the parameter is used for. The parameter names should be the same in all declarations and definitions of the function.
Instead of writing something like vector<string>
over and over
again on variable
and parameter declarations, do typedef vector<string> WordList;
and use WordList
instead. The code will be much easier
to read, and it will trivial to change to another container type
when necessary.
Pointers to functions have a strange syntax. The code becomes much clearer if you use a typedef for the pointer to function type. This typedef name can then be used to declare variables etc.
double sin(double arg); typedef double (*TrigFunc)(double arg); /* Usage examples */ TrigFunc myFunc = sin; void callFunc(TrigFunc callback); TrigFunc funcTable[10];
The syntax for using reference parameters is much cleaner than for using pointer parameters. No dereferencing is required.
Use constant reference parameters to pass a data structure to a function that does not modify the data structure.
Use reference parameters to pass a data structure to a function that does modify the data structure.
Use pointer parameters with dynamically allocated data structures.
Exception specifications in C++ are not as useful as they look. The compiler does not make the code more efficient. On the contrary, the compiler has to insert code to check that called functions do not violate the specified exception specification at runtime.
An inherited function is implicitly virtual if it is declared virtual in the base class. Repeat the virtual keyword when declaring an inherited function in a derived class to make it clear that this function is virtual.
Global variables are initialised when the program starts whether it will be used or not. If global variables are using other global variables for their initialisation, there may be a problem if the dependent variables are not initialised yet. The initialisation order of global variables in different object files is not defined.
Use singleton objects instead, if you can't avoid it. A singleton object is only initialised when the object is used the first time. Singleton objects do not have the ordering problem as the dependent object will be initialised when it is used. However, watch out for cyclic dependencies in singleton object initialisations.
Global variables and singleton objects hide what functions do, because side effects on globals and singleton objects are invisible in the declaration of the functions. To make it clear what inputs and outputs a function has, pass these objects as parameters to the functions.
Also, if the program may become multithreaded, it may be difficult to re-write the program to remove the global variables and singleton objects that cannot be shared between the threads.
using
declarations or directives in
headers.Bringing in names from a namespace to the global namespace may cause conflicts with other headers. The author of a header does not know in which context the header is used and should avoid polluting the global namespace. Instead, only use using declarations in the source files.
Instead of this:
class Employee { public: Employee(string name, string address) { myName = name; myAddress = address; } ... private: string myName, myAddress; };
write this:
class Employee { public: Employee(string name, string address) : myName(name), myAddress(address) { } ... private: string myName, myAddress; };
Initializer lists are both shorter and often more efficient than assignments. With assignments, a variable is first declared and given a default value that is then overwritten. With initializer lists, the variable is created with the desired value directly. This can be significantly more efficient when variables are for complex user types.
explicit
with single-parameter constructors to avoid accidental
implicit type conversions.Consider this example from Stroustrup's The C++ Programming Language:
class String { public: String(int n); // initialize a String with n bytes String(const char *p); // initialize a String with a C string ... }; ... String s = 'a';
Presumably the intention was to construct a String
with
"a"
. But because there's a character not a string, C++
instead converts 'a'
to an integer, and
calls the first String
constructor to make a String
with 'a'
bytes! This is called implicit type conversion.
If you put explicit
in front of the first constructor
(not the second!), then this unintended conversion won't happen.
This makes it easy to read the class definition as the public interface is of interest to most readers.
Classes should encapsulate their data and only provide access to this data by member functions to ensure that data in class objects are consistent.
The exception to the rule is a C-style struct that only contains data members.
Use struct
for small convenience objects holding related data
for each access and transport, e.g., an X-Y coordinate point or the
location and size of a rectangle.
A class definition can be kept small and less prone to change if it only defines the core functionality. Any other functions that can be implemented with this minimal class definition should be implemented as non-member functions. They are still seen as part of the interface of the class.
For example:
class T { T operator+=(const T & right); }; T operator+(const T & left, const T & right) { T temp(left); temp += right; return temp; }
When you define a member function in the class definition, you make the function inline. That means that every call to the member function will be replaced by the body of the function. This leads to bloated compiled code.
Except for very short functions, e.g., one-liners, you should only declare member functions inside a class definition. The definitions should be separate. This applies to both regular and templated classes.
For example, instead of
class Example { public: double currentTotal() // a definition { ... code to calculate total } };
you should write
class Example { public: double currentTotal(); // a declaration }; double Example::currentTotal() { ... code to calculate total }
For regular classes, definitions should go in a separate .cpp file. For templated classes, definitions should go in the header file, after the class definition.
Gotos violate structured coding principles.
A break statement is a goto statement in disguise and makes code less readable.
A break statement is fine and often unavoidable in switch statements.
A continue statement is a goto statement in disguise and makes code less readable.
Even if there is no action for the default label, include one to show that the programmer has considered values not covered by case labels. If the case labels cover all possibilities, it may be useful to put an assertion there to document the fact that it is impossible to get here. An assertion also protects from a future situation where a new possibility is introduced by mistake.
do-while loops are less readable than ordinary while loops and for loops since the conditional is at the bottom of the loop. The reader must scan the entire loop in order to understand the scope of the loop.
In addition, any do-while can easily be rewritten into a while loop or a for loop. Reducing the number of constructs used enhances readability.
Use named constants instead of literal numbers to make the code consistent and easy to maintain. The name of the constant is also used to document the purpose of the number.
For class objects there may be two different member functions for the postfix and prefix operations. The postfix operation has to keep a temporary return value of the object before changing the object. For built-in objects this does not matter as the compiler will be able to optimise away the temporary value when it is not used.
Use dynamic_cast, const_cast, reinterpret_cast and static_cast instead of the traditional C cast notation. These document better what is being performed.
Most macros can be replaced by constants, enumerations or inline functions. Macros do not provide type safety and debugger support.
Comments? Send mail to c-riesbeck@northwestern.edu.
Generated 2007-03-29 by Coding Standard Generator version 1.13.