GlueX Coding Standards

Draft 0.2 - Nov. 18, 2009


This document derived from the CLEO III C++ coding conventions, copyright 1996 by Cornell University.

The following statement is part of the original CLEO document

Portions of this document are derived from Programming in C++: Rules and Recommendations Copyright 1990-1992 by Ellemtel Telecommunication Systems Laboratories.

Permission is granted to any individual or institution to use, copy, modify, and distribute this document, provided that this complete copyright and permission notice is maintained intact in all copies.

Ellemtel Telecommunication Systems Laboratories makes no representations about the suitability of this document or the examples described herein for any purpose. It is provided "as is" without any expressed or implied warranty.

Cornell University and the CLEO collaboration make no representations about the suitability of this document or the examples described herein for any purpose. It is provided "as is" without any expressed or implied warranty.

$Id: c++std.html,v 1.10 1996/04/23 19:49:02 dsr Exp $




Contents

  1. Introduction
  2. Style Recommendations
  3. Rules
  4. Recommendations
  5. Portability Recommendations

Introduction

This document covers specific style recommendations and C++ coding rules to be used when developing common-use GlueX software. The intent is to guide the development of the software base so that it is robust, portable, and presents a consistent style throughout. This will allow easier maintenance and more transparent operation for the GlueX users.

This document intends to build upon the ISO/ANSI C++ standard and that standard should be considered the prologue to this. As the ISO/ANSI standard changes, so does the GlueX coding standard. The ISO/ANSI C++ standard is superceeded only when the standard compler on the most common use systems does not yet support the specific feature.

This document is made up of several sections. The Style Recommendations are placed first to emphasize them. Although only recommendations, they should be adhered to whenever possible as this will provide the most consistency and readability of the code. The Rules section could equally well be labeled "Best Practices" as they general in nature and not really specific to GlueX.

Style Recommendations

Programming style can provide a consistency and readability to code that allows it to be maintained and modified easily by persons who are not the original authors. It also makes it easier for persons who simply need to understand the precise functionality of the code for purposes of analysis. As such, it is strongly recommended that these style guidlines be followed wherever possible.

  1. The style within a single module must be consistent. Where choices are allowed, one option must be chosen consistently within a module. Always respect the style decisions of the original author.

    This is the only absolute requirement in this section.

  2. Class names should begin with an uppercase letter D to signify Hall-D specific code. The second letter should also be capitalized except when the class is a minor utility class (e.g. defined within another class) in which case the letter after the initial "D" should be lowercase.

  3. Class member functions should begin with an uppercase letter.

  4. Class member variables should begin a lowercase "m". A single leading underscore is not recommended.

  5. Constants (including enums) should begin with a lowercase c.

  6. Global variables are strongly discouraged and should only be used in exceptional circumstances. When they are required, they should begin with a lowercase g followed by a capital letter.

  7. In names composed of more than one word, the name should follow a standard camelcase format.

  8. Do not use spaces around `.' or `->', nor between unary operators and operands.

  9. Indentation should generally be done using tabs (not spaces). Editors should be set to display 3 spaces per tab so the display formatting appears the same to viewers as it did to the original author.

Rules

  1. Every time a rule is broken must be clearly documented. No exceptions.

    "This module violates rule 1" is not allowed.

  2. Write thread-safe code.

    This mainly means you should not use global variables.

  3. {need re-write of standard header section}
  4. Every include file must be protected against multiple inclusion with a #define of the form _<class>_, e.g.

    #ifndef _DTrackFitter_
    #define _DTrackFitter_
    
    // ...
    
    #endif  // _DTrackFitter_
    
  5. All comments and names are to be written in English.

  6. Never use ".." in #include directives.

  7. Do not use names that differ only by the use of uppercase and lowercase letters.

  8. Do not use identifiers which begin with two underscores (`__') or one underscore (`_') followed by a capital letter. Do not use identifiers with global or file scope which begin with one underscore (`_').

    These are all reserved to the compiler by the ANSI standard.

  9. All classes should be declared following the GlueX standard class skeleton.

    Public, protected, and private are to declared explicitly in that order; do not use default access control. By placing the public section first, everything that is of interest to a user is gathered in the beginning of the class definition. The protected section may be of interest to designers when deriving from the class. The private section contains details that should have the least general interest.

    class DTrackFitter {
       // friend classes and functions
       public:	
          // constants, enums and typedefs
          // Constructors and destructor
          DTrackFitter(JEventLoop *loop);
          virtual ~DTrackFitter();
    
       protected:
          // protected member functions
          // protected const member functions
       private:
          // private member functions
          // private const member functions
          // data members
          // static data members
    };
    
  10. No non-empty member functions are to be defined within the class definition.

    Class definitions are less compact and more difficult to read when they include definitions of member functions. It is also easier to change an inline member function to an ordinary member function if the definition of the inline function is placed outside of the class definition.

    The only exception is for "trivial" functions which are guaranteed to not change, such as accessor functions to data whose representation is fixed, or forwarding functions. For example:

    int GetNdof(void) const {return Ndof;} 
    

  11. Member data that may be of interest outside of the class should have an accessor function starting with the string "Get". For example"
    int GetNdof(void) const {return Ndof;}
    
    Similarly, a Set method should be provided for setting member data from outside of the class.
  12. A member function that does not affect the state of an object is to be declared const.

    Member functions declared as const may not modify non-mutable member data and are the only functions which may be invoked on a const object. A const declaration is an excellent insurance that objects will not be modified when they should not be.

  13. const member functions may not alter any data that changes the behavior of the object.

    It is possible for an object to have member data that can be changed without changing the significant behavior of the object, such as use counters used for profiling, or caches. Such member data should be declared with the mutable keyword.

  14. All classes should declare a copy constructor, an assignment operator from its own type, at least one constructor and a destructor. Any that are not used should be declared private and not defined.

    If any of these is not supplied, the compiler will generate a default version. Since the default is not always appropriate, explicit functions should be supplied, or a comment should be made indicating that the default is appropriate.

    In particular, a copy constructor is recommended to avoid surprises when an object is initialized using an object of the same type. If an object manages the allocation and deallocation of an object on the heap (the managing object has a pointer to the object to be created by the class' constructor), only the value of the pointer will be copied. This can lead to two invocations of the destructor for the same object (on the heap), probably resulting in a run-time error. The same applies to the assignment operator.

    Any of these that are not used should be declared private and not defined, so that the compiler or linker will complain if one of them is inadverdantly used or an implicit use is generated by the compiler.

  15. All classes which might possibly be used as base classes must define a virtual destructor.

    If a class, having virtual functions but without virtual destructors, is used as a base class, there may be a surprise if pointers to the base class are used. If such a pointer is assigned to an instance of a derived class and delete is then used on this pointer, only the base class' destructor will be invoked. If the program depends on the derived class' destructor being invoked, the program will fail.

    In general, all classes should define a virtual destructor unless the added size of the virtual function table pointer would be unacceptable

  16. An assignment operator which performs a destructive action must be protected from performing this action on the object it is operating on.

    A common error is assigning an object to itself (a = a, but usually not that obviously). Normally, destructors for instances which are allocated dynamically are invoked before assignment takes place. If an object is assigned to itself, the instance variables will be destroyed before they are assigned. This may lead to strange run-time errors. If a = a is detected, the assigned object should not be changed, e.g.

    const T&
    T::operator=(const T& rhs)
    {
       if (this != &rhs) {
          delete m_t;
          m_t = rhs.m_t;
          // rest of assignment...
       }
       return *this;
    }
    
  17. A public member function must never return a non-const reference or pointer to private member data or to data outside an object.

    This rule may be violated by member functions which relinquish ownership of an object, e.g. from container classes. In all cases, it must be clear where ownership of every object resides.

  18. Do not use unspecified function arguments (ellipsis notation, varargs).

    Unspecified function arguments break type safety. Use overloaded functions or default arguments instead.

  19. The names of formal arguments to functions are to be specified and are to be the same both in the function declaration and in the function definition.

    Function argument names should be considered to be part of the documentation of the class interface. Function argument names should be chosen accordingly, and should appear in the declaration as that is where the class interface should be documented.

    Argument names may be omitted in the declaration if the usage is trivially obvious. However, this practice is discouraged where it may lead to ambiguity, for example

        void f(const T); // is this 'const int T'
                         //      or 'const T x'?
    
  20. Always specify the return type of a function explicitly.

    Implicit typing should always be avoided. Similarly, unsigned int x; is preferred to unsigned x;

  21. A function must never return a reference or a pointer to a local variable.

    If a function returns a reference or a pointer to a local variable, the memory to which it refers will already have been deallocated when the reference or pointer is used.

  22. Avoid using #define wherever possible.

    Use inline functions (where necessary) for short functions; use const or enum to define constants. Acceptable uses for #define are to protect header files from multiple inclusion and for "feature-test" defines.

  23. Avoid the use of "magic" numeric values in code; use symbolic values instead.

    Numerical values in code ("Magic Numbers") should be viewed with suspicion. They can be the cause of difficult problems when it becomes necessary to change a value. A large amount of code can be dependent on such a value never changing, the value can be used at a number of places in the code (it may be difficult to locate all of them), and values as such are rather anonymous (it may be that every `2' in the code should not be changed to a `3').

    Magic numbers also provide no information about why that numbers is used there. Well chosen symbolic names are much more useful for the reader and maintainer.

  24. Use initialization instead of assignment wherever possible.

    By always initializing variables, instead of assigning values to them before they are first used, the code is made more efficient since no temporary objects are created for the initialization. For objects having large amounts of data, this can result in significantly faster code.

    Example

    DO THIS:

    double masses[3] = {0.1396, 0.4937, 0.9383};

    NOT THIS:

    double masses[3];
    masses[0] = 0.1396;
    masses[1] = 0.4937;
    masses[2] = 0.9383;
    
  25. List members in an constructor initialization list in the order in which they are declared.

    Initialization lists are executed in the order the members were declared in, not the order they are listed in. Listing initializers in declaration order avoids any confusion.

  26. Avoid explicit casts between classes.

    C style casts should be avoided at all times. Where casts are necessary, dynamic_cast is preferred since it is type safe. static_cast and const_cast may be used sparingly (but appropriate use of mutable is preferred to const_cast). reinterpret_cast should be avoided whenever possible.

  27. Do not rely on implicit type conversion functions in function argument lists.

    C++ is lenient concerning the variables that may be used as arguments to functions. If there is no function which exactly matches the types of the arguments, the compiler attempts to convert types to find a match. If more than one matching function is found, a compilation error may result. Existing code which the compiler had allowed may suddenly contain errors after a new implicit type conversion is added to the code.

    Another unpredictable effect of implicit type conversions is that temporary objects may be created during the conversion. The temporary object is then the argument to the function, not the original object.

    Be careful with constructors that use only one argument, since this introduces a new type conversion which the compiler can unexpectedly use. Single argument constructors that should not be used for implicit type conversions should be declared with the explicit keyword.

  28. Never convert a const to a non-const.

    Data members that should be modifiable even in const objects should be declared mutable.

  29. The code following a case label or group of case labels must be terminated by a break statement.

    If the code which follows a case label is not terminated by break, the execution continues after the next case label. This program flow is confusing and hence should be avoided.

    When several case labels are followed by the same block of code, only one break statement is needed.

  30. A switch statement must contain a default case.

  31. Never use goto.

    Goto breaks the control flow and can lead to code that is difficult to comprehend.

  32. Do not use malloc, realloc or free.

    In C, malloc, realloc and free are used to allocate memory dynamically on the heap. This may lead to conflicts with the use of the new and delete operators in C++, and can limit the effectiveness of overloading global new with a custom allocator.

  33. Do not allocate memory and assume that it will be deallocated for you.

    However, in some places, it may be documented that an object will be freed e.g. at the end of the event or end of run.

Recommendations

  1. Give a file a name that is unique in as large a context as possible.

  2. An include file for a class declaration should have a file name of the form class name + extension. Use uppercase and lowercase letters in the same way as in the source code.

  3. When possible, do not #include the definitions of classes that are only accessed via pointers (*) or references (&).

    Using a forward declaration instead can reduce the number of include files that need to be parsed and reduce the impact of changes.

    Example:

    class DTrackFitter;
    as opposed to:
    #include <TRACKING/DTrackFitter.h>

  4. Write comments to describe the overall purpose/use of a function before every function.

    Also, write descriptive comments within the code to describe what it's doing, even if it seems "obvious". Consider that someone less experienced than yourself may be asked to modify or maintain your code years from now. As a rule of thumb, shoot for 50% of the file to be comments.

  5. Use doxygen compatible markers (e.g. /// )for comments where appropriate. For comments not intended for external documentation, use // instead of the C-style /* */ .

  6. Every implementation file should declare a local constant string to contain the revision control system ID.

    [should be in skeleton, class should have a member function to dump the RCS ID to an ostream]

  7. Variables are to be declared with the smallest possible scope.

    Variables should be declared as needed and with the smallest feasible scope. This avoids cluttering the variable namespace unecessarily, helps keep declaration and use close together, and ensures that objects are destroyed (and their resources freed) as soon as possible.

  8. Variables should generally have have fully descriptive names. Choose variable names that suggest the usage.

  9. The flow control primitives if, else, while, for and do should be followed by a block (i.e. curly braces) if not completely contained on a single line.

       // ok
       while ( /* Something */ ) statement;
    
       // Single statement on multiple lines - No!
       while ( /* something */ )
          statement;
    
       // ok
       while ( /* something */ ) {
          statement;
       }
    
  10. Think about alternatives before declaring friends of a class.

    Friends can violate encapsulation. While there are situations where friends are appropriate, their use should be limited as much as possible. The use of many friends may indicate that the modularity of the system is poor. When friends are used, it is best to limit the scope by making particular member functions friends instead of the entire class.

  11. Avoid the use of external static objects in constructors and destructors.

    The order of initialization of static objects defined in different compilation units is not fixed by the language, so a static object may not depend on the previous initialization of a static object in a different compilation unit.

  12. An assignment operator ought to return a const reference to the assigning object (usually *this).

    Returning a const reference is most compatible with the behavior of built-in types.

  13. Use operator overloading sparingly and in a uniform manner.

    Operator overloading has both advantages and disadvantages. One advantage is that code which uses a class with overloaded operators can be written more compactly (and possibly more readably). Another advantage is that the semantics can be both simple and natural. One disadvantage in overloading operators is that it is easy to misunderstand the meaning of an overloaded operator if the programmer has not used natural semantics.

  14. When an operator with an opposite (such as == and !=) is defined, the opposite should also be defined. When an operator with associated operators (such as +, -, +=, -=) is defined, all the associated operators should be defined.

    Designing a class library is like designing a language! If you use operator overloading, use it in a uniform manner; do not use it if it can easily give rise to misunderstanding.

  15. Avoid functions with many arguments.

    Functions having long lists of arguments look complicated, are difficult to read, and can indicate poor design. In addition, they are difficult to use and to maintain.

  16. An object in a function argument list should normally be passed as a reference, unless the function stores the value, in which case it should be passed as a pointer. Use constant references (const & in the formal argument list) when possible.

    By default C++, passes arguments by value. Function arguments are copied to the stack via invocations of copy constructors, which can be expensive for large objects, and destructors are invoked when exiting the function. const & arguments mean that only a reference to the object in question is placed on the stack (call-by-reference) and that the object's state (its instance variables) cannot be modified.

    By using references instead of pointers as function arguments, code can be made more readable, especially within the function. A disadvantage is that it is not easy to see which functions change the values of their arguments. Member functions which store pointers which have been provided as arguments should document this clearly by declaring the argument as a pointer instead of as a reference.

  17. Wherever possible, allocated memory should be stored in an object and deallocated in the destructor.

    The ANSI auto_ptr template class can often be used to make this automatic:

    void f()
    {
       auto_ptr<T> p1(new T);
       // ...
    }
    

    The T pointed to by p1 will be automatically deleted when p1 goes out of scope (when f() exits). This style of "declaration as allocation" is very useful when exceptions are used.

  18. When overloading functions, all variations should have the same semantics.

    Overloading of functions can be a powerful tool for creating a family of related functions that only differ as to the type of data provided as arguments. If not used properly (such as using functions with the same name for different purposes), they can cause considerable confusion.

  19. inline functions only when necessary.

    Access and forwarding functions may be inline if they are unlikely to ever change. Excessive use of inlining will force excessive recompilation when the implementation of those inline functions changes.

  20. Prefer polymorphism to switch statements in class member functions.

  21. Use unsigned for variables which cannot have negative values.

  22. Avoid global data.

  23. Use standard library functions.

Portability Recommendations

  1. Code that requires a type of a particular size should typedef a local type from a system typedef of the appropriate size. Do not assume that the built-in types have particular sizes.

  2. Use explicit type conversions for arithmetic using signed and unsigned values.

  3. Do not assume that you know how an instance of a data type is represented in memory.

  4. Do not assume that long, float, double or long double types may begin at arbitrary addresses.

  5. Do not depend on underflow or overflow functioning in any special way. [what can one depend on?]

  6. Do not assume that the operands in an expression are evaluated in a definite order. [value modified twice, functions with side effects]

  7. Do not assume that you know how the invocation mechanism for a function is implemented.

  8. Do not assume that static objects in different files are initialized in any special order.