Next: , Previous: , Up: Coding Style   [Contents][Index]

2.4.3 File Conventions

There are some strict conventions to obey wrt the files and their contents.

Rule: One class LikeThis per files like-this.*

Each class LikeThis is implemented in a single set of file named like-this.*. Note that the mixed case class names are mapped onto lower case words separated by dashes.

There can be exceptions, for instance auxiliary classes used in a single place do not need a dedicated set of files.

Rule: *.hh: Declarations

The *.hh should contain only declarations, i.e., prototypes, extern for variables etc. Inlined short methods are accepted when there are few of them, otherwise, create an *.hxx file. The documentation should be here too.

There is no good reason for huge objects to be defined here.

As much as possible, avoid including useless headers (GotW007, GotW034):

Rule: *.hxx: Inlined definitions

Some definitions should be loaded in different places: templates, inline functions etc. Declare and document them in the *.hh file, and implement them in the *.hxx file. The *.hh file last includes the *.hxx file, conversely *.hxx first includes *.hh. Read below.

Rule: *.cc: Definitions of functions and variables

Big objects should be defined in the *.cc file corresponding to the declaration/documentation file *.hh.

There are less clear cut cases between *.hxx and *.cc. For instance short but time consuming functions should stay in the *.cc files, since inlining is not expected to speed up significantly. As another example features that require massive header inclusions are better defined in the *.cc file.

As a concrete example, consider the accept methods of the AST classes. They are short enough to be eligible for an *.hxx file:

LetExp::accept(Visitor& v)

We will leave them in the *.cc file though, since this way only the *.cc file needs to load ast/visitor.hh; the *.hh is kept short, both directly (its contents) and indirectly (its includes).

Rule: Explicit template instantiation

There are several strategies to compile templates. The most common strategy consists in leaving the code in a *.hxx file, and letting every user of the class template instantiate the code. While correct, this approach has several drawbacks:

To circumvent these problems, we may control template instantiations using explicit template instantiation definitions (available since C++ 1998) and declarations (introduced by C++ 2011).

This mechanism is compatible with the way templates are usually handled in the Tiger compiler, i.e., where both template declarations and definitions are accessible from the included header, though often indirectly (see above). We use the following two-fold strategy:

You will notice that both the approach and the syntax used here recall the ones used to declare and define global variables in C and C++.

We can further improve the previous design by factoring explicit instantiation code using the preprocessor.

 ** \file temp/temp.hh
 ** \brief Fresh temps.

#pragma once

#include <misc/endomap.hh>

# define MAYBE_EXTERN extern

namespace temp
  struct Temp { /* ... */ };

// ...

namespace misc
  // Explicit template instantiation declaration.
  MAYBE_EXTERN template class endo_map<temp::Temp>;

File 2.3: temp-factored.hh

 ** \file temp/
 ** \brief temp::Temp.

#include <temp/temp.hh>

// ...

File 2.4:

Explicit template instantiation declarations (not definitions) are only available since C++ 2011. Before that, we used to introduce a fourth type of file, *.hcc: files that had to be compiled once for each concrete template parameter.

Rule: Guard included files (*.hh & *.hxx)

Use the ‘#pragma once’ directive to ensure the contents of a file is read only once. This is critical for *.hh and *.hxx files that include one another.

One typically has:

 ** \file sample/sample.hh
 ** \brief Declaration of sample::Sample.

#pragma once

// ...

#include <sample/sample.hxx>

File 2.5: sample/sample.hh

 ** \file sample/sample.hxx
 ** \brief Inlined definition of sample::Sample.

#pragma once

#include <sample/sample.hh>

// ...

File 2.6: sample/sample.hxx

Rule: fwd.hh: forward declarations

Dependencies can be a major problem during big project developments. It is not acceptable to “recompile the world” when a single file changes. To fight this problem, you are encouraged to use fwd.hh files that contain simple forward declarations. Everything that defeat the interest of fwd.hh file must be avoided, e.g., including actual header files. These forward files should be included by the *.hh instead of more complete headers.

The expected benefit is manifold:

Consider for example ast/visitor.hh, which is included directly or indirectly by many other files. Since it needs a declaration of each AST node one could be tempted to use ast/all.hh which includes virtually all the headers of the ast module. Hence all the files including ast/visitor.hh will bring in the whole ast module, where the much shorter and much simpler ast/fwd.hh would suffice.

Of course, usually the *.cc files need actual definitions.

Rule: Module, namespace, and directory likethis

The compiler is composed of several modules that are dedicated to a set of coherent specific tasks (e.g., parsing, AST handling, register allocation etc.). A module name is composed of lower case letters exclusively, likethis, not like_this nor like-this. This module’s files are stored in the directory with the same name, which is also that of the namespace in which all the symbols are defined.

Contrary to file names, we do not use dashes to avoid clashes with Swig and namespace.

Rule: libmodule.*: Pure interface

The interface of the module module contains only pure functions: these functions should not depend upon globals, nor have side effects of global objects. Global variables are forbidden here.

Rule: tasks.*: Impure interface

Tasks are the place for side effects. That’s where globals such as the current AST, the current assembly program, etc., are defined and modified.

Next: , Previous: , Up: Coding Style   [Contents][Index]