wiki:C++Standard/Files
Last modified 8 years ago Last modified on 10/05/2011 01:25:07 PM

Draft for new release V 1.6

This document is being updated to reflect changes approved by the DM TCT. The document is in transition.

The previous official version of the C++ Programming Style Guildelines (V 1.5) is available here.

LSST Data Management: C++ Programming Style Guidelines


4 Files

4.1 Source Files

4-1.a The head of each .h and each .cc file MUST begin with a fixed format comment declaring the file contents and format to emacs.

// -*- LSST-C++ -*-

This solved the emacs problem of not recognizing a C++ header file ending in .h. Vim use is not affected.

4-1.b C++ header files MUST have the extension .h (preferred) or .hpp (allowed).

myClass.h, myClassa.hpp

These are accepted C++ standards for file extension.

4-1.c C++ source files MUST have the extension .cc .

myClass.cc

These are accepted C++ standards for file extension.

4-2. Files that contain a single class SHOULD have the name of the class, including capitalization.

MyClass.h, MyClass.cc

Makes it easy to find the associated files of a given class. This convention is enforced in Java and has become very successful as such. In general, there should be one class declaration per header file. In some cases, smaller related classes may be grouped into one header file.

4-3. Member functions and data SHOULD be listed logically.

For example, all constructors should be grouped together, all event handling routines should be declared together, as should the routines which access member data. Each logical group of functions should have a common comment above the group explaining why they are grouped together.

4-4.a All non-templatized class/function definitions except inlines SHOULD reside in source files.

class MyClass {
public:
    int doSomethingComplicated() { // NO!
        float a = exp(-h*nu/(k*T));
        float foobar = computeFooBar(a,PI/4);
        ...
        return value;
    }

}

The header files should declare an interface, the source file should implement it. When looking for an implementation, the programmer should always know that it is found in the source file. The obvious exception to this rule is of course inline functions that must be defined in the header file (see next rule).

4.4.b All templatized classes/functions, MUST be explicitly instantiated, i.e. the type declared and the instances to be used explicitly instantiated in the header file, the type members are defined in the source file.

Also, template instantiations should be declared extern to ensure that the compiler and programmers know which instantiations are intended.

In MyString.h:

template <typename CharType>
class MyString {
//...
}
extern template class MyString<char>;//Inhibits implicit MyString<char>

We expect to freely use template classes in the framework and possibly elsewhere in the application layer. There will be many template class declarations and many instantiations of them. On the other hand, we want to preserve the separation of interface (declaration) in .h files from implementation (definition) in .cc files.

The solution is explicit template instantiation. This requires that the specific template instantiations (classes) that are to be used be compiled into a library, and then the .h files can remain separate, as long as they explicitly declare which template instantiations will be used. In explicit template instantiation the compiler and linker handle the details of this process for you. You can also set the compiler to prohibit any implicit template instantiations (with no-implicit-templates) to prevent accidental double definitions.

This works quite well for those using a framework and not extending it, i.e. one knows the template instantiations available to the application at compile time and does not create new instantiations, one just uses the ones that are already defined. For most applications that are not extending the framework, this should be pretty clear and will probably work quite well. It is less clear for the framework itself, but we can always rely on the linker to tell us when we have goofed and allowed something to be doubly defined.

4-5. Inline functions SHOULD be avoided except for simple accessors/mutators that get or set a simple attribute or empty constructors/destructors.

If used, inline functions should be very simple. If an inline function has a body of more than 5 lines, it should be placed outside the class definition.

#ifndef LSST_FOO_H
#define LSST_FOO_H

class Foo {
public:
    Foo();
    virtual ~Foo() {}
    int getValue() const { return _value; }
    inline int getAnotherValue() const;

private:
    int _value;
    int _anotherValue;
};

int Foo::getAnotherValue() const {
  return _anotherValue;
}

#endif // LSST_FOO_H

Empty constructor

explicit IdSpan(int id, int y) : id(id), y(y) {}

When choosing whether to inline, think about balancing compile-time and run-time performance. Be careful to avoid requiring inclusion of additional .h files; use forward declaration if needed. See Myers, Effective C++ 3rd Ed., item 30.

4-6. File content MUST be kept within 110 columns.

The restriction to 80 columns is no longer as much a consideration as a common dimension for editors, terminal emulators, printers and debuggers, and so on. However, even with multi-window environments and current displays it is often useful to have multiple source windows open side by side, and limiting the number of characters facilitates this. It improves readability when unintentional line breaks are avoided when passing a file between programmers.

4-7. Use of special characters like TAB, carriage-return (ctrl-M) and page break are PROHIBITED.

These characters can cause problem for editors, printers, terminal emulators or debuggers when used in a multi-programmer, multi-platform environment.

4-8. The incompleteness of split lines MUST be made obvious.

Emacs indentation rules are suggested. As a minimum, indent the continuation at least 4 spaces. Indentation to the right of an opening parenthesis that has not yet been closed or an assignment operator is also permitted.

totalSum = a + b + c +
           d + e;
function(param1, param2,
         param3);
setText("Long line split"
        "into two parts.");
for (tableNo = 0; tableNo < nTables;
     tableNo += tableStep)

Split lines occur when a statement exceeds the 110 column limit given above. It is difficult to give rigid rules for how lines should be split, but the examples above should give a general hint.

In general:

  • Break after a comma.
  • Break after an operator.
  • Align the new line with the beginning of the expression on the previous line.

Other Applicable Rules

Additional comments on source layout are available in "Chapter 3: Naming Conventions". In particular, namespace layout is discussed in Rule 3-6.

4.2 Include Files and Include Statements

4-9. Header files MUST include a construction that prevents multiple inclusion.

The convention is an all uppercase construction of the full namespace, the file name and the h suffix.

For a file named AntennaRx?.h:

#ifndef LSST_ANTENNA_RX_H            // referring to file: AntennaRx.h
#define LSST_ANTENNA_RX_H
 ...
#endif // LSST_ANTENNA_RX_H

The construction is to avoid compilation errors. The construction must appear in the top of the file (before the file header) so file parsing is aborted immediately and compilation time is reduced.

4-10. Include statements SHOULD be sorted and grouped.

Groups are sorted by dependency (foo.h before bar.h if bar.h depends on foo.h) then alphabetically. Leave an empty line between groups of include statements. Place C includes first if any, then C++. Try to minimize dependencies and include the minimum required.

#include <fstream>
#include <iomanip>

#include "ui/MainWindow.h"
#include "ui/PropertiesDialog.h"

#include <Xm/ToggleB.h>
#include <Xm/Xm.h>

In addition to showing the reader the individual include files, it also gives an immediate clue about the modules that are involved.

Include file paths must never be absolute. Compiler directives should instead be used to indicate root directories for includes.

4-11. Include statements SHOULD be located at the top of a file only.

In the case of the implementation (.cc file) of a template definition (.h file) the include statement may be placed at the end of the including file.

Common practice. Avoid unwanted compilation side effects by "hidden" include statements deep into a source file.

4-12. There MUST be no unused include files listed in the source.

This avoids unwanted compilation side effects and reduces compilation time.

4-13. 'Using' declarations and 'using' directives MUST not be used in header files.

Example using declaration:   using lsst::canbus::CanIo
Example using directive:   using namespace lsst::canbus

A using declaration adds a name to the local scope. This is bound to create a conflict. Using directives are less likely to cause conflicts, since the compiler will force the user to qualify the name. However, code is generally clearer and more precise if they are not used in header files.

4-14. There SHOULD be a header file for each library that has the name of the library and includes all of the include files necessary to define the public interface.

#include "lsst/util.h"

Having a single include file per library makes it easier for application developers to ensure they include all the headers files they need. It also puts the burden to keep the library header files up to date on the library developers where it belongs. Applications can use these files, but library files should reference individual include files explicitly.

4-15. Only system include file paths SHALL be delimited with '<>'

'< >' should be used to delimit include file paths only for products installed in the system locations.

Double quotes should be used to delimit those paths which refer to any code installed in an LSST distribution location; this includes the packages from the LSST repository and all 3rd party products installed in the LSST distribution tree.

#include  "boost/any"
#include "lsst/afw/image/image.h"
#include "vw/image.h"

"<>" includes search system paths before local paths. It is slightly less efficient to use "<>" with non-system headers, which should only be searched in '-I' directories and the current directory.