In C++ Code, always use C++ style comments. Use C style comments only in headers which are used both in C and C++ code. Here is an example:
// // This otherwise silly comment has a blank line in the middle // // and at the beginning and at the end //
__ in names to separate words, as in
// USE this style int this_is_the_answer = 42; // NOT this style int thisIsATheAnswer = 42;
This will make your program more readable.
GT_ and
GT_Tcl.
GT_Graph,
GT_Tcl_Graph.
father,
son, next_son.
static member functions instead:
class foo {
public:
static bar ();
}
// Later in the progam ...
foo::bar();
Dont use global C style functions. Static member functions are much safer against collisions.
C++ header files must be organized as follows:
.h. Its base name
must be the same as the corresponding .cpp file.
.h/.cpp pair
for each class.
#ifndef GT_MODULE_H // module is the name of the file #define GT_MODULE_H Header … Code #endif
Header files should contain only the following:
.cpp file.
inlined methods. Non-inlined method
implementations must never be placed into header files.
Each class which has member variables must have a constructor. If there is more than one constructor, consider writing a single method for initializing the class, which is used by the constructors.
Virtual Destructors must be used whenever a class has virtual functions. It is a good idea to make all destructors virtual except for trivial classes.
Notes:
Use virtual methods whenever appropriate so that derived classes can modify the behavior of objects. In generall, one should provide as many customization options as possible. Here are some hints:
virtual functions so
that these functions can easily be redefined in a derived class.
Member variables in a class must never be public. Instead, access to member variables must be through dedicated procedures, accessors. The example below shows how to properly implement accessors:
class GT_foo : public GT_bar {
//
// Graphlet standard class declaration
//
typedef baseclass GT_bar;
private:
//
// Member variables
//
int the_parameter;
GT_Complex_Datatype the_solution;
public:
//
// Constructors and Destructors
//
// The destructor is virtual because the base class GT_bar
// might have virtual methods.
//
GT_foo ();
virtual ~GT_foo ();
//
// Methods which set member variables
//
void parameter (const int p) {
this->the_parameter = p;
}
void solution (const GT_Complex_Datatype& s) {
this->the_solution = s;
}
//
// Methods which retrieve the values of member variables
//
inline int parameter () const {
return this->the_parameter;
}
inline const GT_Complex_Datatype& solution () const {
return this->the_solution;
}
}
Accesssors must observe the following rules:
the_the_.
GT_Complex_datatype in the above example) must return
const reference.
Your classes must be const correct. If you do not know
about const correctness, see the C++ literature. The following
is a short sketch of what const correctness means. Const correct
code uses the const keyword to show that
Here are some examples of const correct code:
int SomeClass::x() const {
return this->the_x;
}
The function x() returns a copy of the value of
the member variable the_x. Since it does not change
the object, it is declared const.
Obviously, a const method must not call any non-const method
on the same object.
const reference:
void SomeClass::copy (const SomeClass& object)
{
*this = object;
}
Here, object is declared as a reference to a constant object
of class SomeClass, since object is not changed
in copy. This is the default way of declaring a
copy constructor.
Note:
If copy is called with a temporary object, as in
aNewObject.copy(SomeClass(42)); then C++
requires a constant reference in the copy
constructor. Modern compilers are able to check
that.
const SomeClass& SomeClass::something()
{
return this->the_something;
}
The object returned by something() cannot be modified,
so side effects are effectively ruled out.
A const method may call only other const
methods, as illustrated below:
int SomeClass::x() const
{
return this->x;
}
int SomeClass::y() // NOT const
{
return this->y;
}
int SomeClass::f() const
{
return x(0) + y(); // ERROR: y is not declared const
}
int SomeClass::g()
{
return x(0) + y(); // OK
}
The same holds for operations on a const object:
int SomeOtherClass::g(const SomeClass& object) const
{
// ERROR: object is const,
// but y is not const
return object.x(0) + object.y();
}
It is sometimes necessary to declare a constant and a non-constant version of a function:
Something& SomeClass::something() // NOT const
{
return this->something;
}
const Something& SomeClass::something() // const
{
return this->something;
}
C++ allows to cast const away, but that is considered evil and
should not be used. It may even happen that the a constant and a
non-constant object (such as an STL iterator) are implemented differently !
Use defensive programming whenever possible. Defensive programming means:
any function must act gracefully if there is an error. For example,
functions must check for 0 pointers or empty strings.
As a rule of thumb, a function must have a defined behavior for all
reasonable parameter values, even unexpected ones.
You should use the assert macro to insert assertions:
#includevoid SomeClass::set (int* x) { assert(x != 0); the_x = x; }
This causes the program to print an error message and core dump whenever
x == 0 at runtime.
Of course, assert must only be used to check
conditions which must not occur.
Of course, an easier way to avoid checking for 0 is to define
the method set as follows:
void SomeClass::set (const int& x)
{
this->x = x;
}
Another way to deal with that problem are C++ exceptions. An exception is a C++ structure that provides graceful handling of emergency situations. However, several compilers still have problems with their implementation of exception handling, so we will not use exception handling at this time.
Notes:
assert statements are also a way to document programs.
sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)
Dont use Microsoft's int32 or similar data types. They are not portable.
char and
unsigned char.
void*, but not the other
way round.
void*. This is not a bug but a C++ feature.
Duplicate code must be avoided whenever possible. Pack duplicate code into a single method or function, and call this method or function instead. If you have good reasons that this cannot be done for optimization reasons, consider using an inline function or - in extreme cases - a preprocessor macro. The same should be done with similar code pieces. Use a function or method with an additional parameter or a macro.
Avoiding duplicate code will not only provide a cleaner code, but eases maintenance quote a lot.
Use function and operator overloading when appropriate. Especially operator overloading can help to write code that is better to understand.
Notes:
#include directives:
GNUMakefile:
-I/usr/local/xxx/yyy-version/include
to set the search path for include files.
-L/usr/local/xxx/yyy-version/lib to
set the search path for libraries. It is easier to change the files in your
makefile than in your code.
-I.-I. to your compiler options.
It is neither necessary nor good practice to include the current directory
explicitly in the search path. To include a file from the current
directory, use #include "file.h" rather than
#include .
-Idirectory, never add a subdirectory of
directory explicitly. Use the format
#include
instead. This will also help others to find out where file.h
comes from.
Avoid the preprocessor. Avoid C++ preprocessor macros at
al costs. Use C++ const and enum declarations
or inline functions instead of preprocessor macros
whenever possible. They are much easier to use. Errors in
preprocessor macros are often hard to find, and name conflicts
between C++ names and preprocessor macros are hard to find.
Note: If you suspect a problem with a preprocessor macro,
you can use the compiler flag -E to run only the preprocessor.
In general, one can replace macros by enums, consts and (inlined)
functions. const, enum and inline
functions are C++ elements and their semantics can be checked by
the compiler, whereas macros are preprocessor elements and cannot
be checked. The C++ constructs
const unsigned int universal_solution = 42;
enum {
Color_red, // dont use red, white, blue for a global name
Color_white,
Color_blue
}
Color;
inline int width(int w)
{
return w+42;
}
are save replacements for
#define UNIVERSAL_SOLUTION 42 #define RED 1 #define WHITE 2 #define BLUE 3 #define width(w) ((w)+42)
If you really need to use a macro, use as much braces as you can think of:
// Correct #define width(w) ((w)+42) // WRONG #define width(w) w+42
Think about 100-width(50).
It is often useful and generally safer to use references instead of pointers:
// USE this if you can void SomeClass::f (const structure& reference); // Not so much recommended void SomeClass::method (const structure* pointer);
Notes:
Use inline functions only for very short and often used functions. Never use them for functions with more than a few lines. When in doubt, use a normal function.
Note:
inline is only a hint to the compiler, and many compilers will
inline only during optimization (that is, compiler flag -O or
similar).
inline within
the class definition. Otherwise, an inlined function might be used before
declared inline.
private: section of a class declaration. Also, classes which
are used in a single module should be declared as local in this module.
Here is a list of really out-of-date features which should not be used anymore these days:
NULLNULL for nil pointers. C++ uses
0:
// OK char* x = 0; // Wrong char* x = NULL;
void proc (void)(void). C++
uses the more intuitive ():
// OK void procedure (); / Wrong void proc (void);
#define CONSTANT#define to define constants. Use the
const keyword instead; this is generally easier to read and
avoids hard-to-track errors.
CAPITAL_LETTERS