SF: Source files

Distinguish between declarations (used as interfaces) and definitions (used as implementations). Use header files to represent interfaces and to emphasize logical structure.

Source file rule summary:

SF.1: Use a .cpp suffix for code files and .h for interface files if your project doesn't already follow another convention

Reason

It's a longstanding convention. But consistency is more important, so if your project uses something else, follow that.

Note

This convention reflects a common use pattern: Headers are more often shared with C to compile as both C++ and C, which typically uses .h, and it's easier to name all headers .h instead of having different extensions for just those headers that are intended to be shared with C. On the other hand, implementation files are rarely shared with C and so should typically be distinguished from .c files, so it's normally best to name all C++ implementation files something else (such as .cpp).

The specific names .h and .cpp are not required (just recommended as a default) and other names are in widespread use. Examples are .hh and .cxx. Use such names equivalently. In this document we refer to .h and .cpp as a shorthand for header and implementation files, even though the actual extension may be different.

Example
// foo.h:
extern int a;   // a declaration
extern void foo();

// foo.cpp:
int a;   // a definition
void foo() { ++a; }

foo.h provides the interface to foo.cpp. Global variables are best avoided.

Example, bad
// foo.h:
int a;   // a definition
void foo() { ++a; }

#include<foo.h> twice in a program and you get a linker error for two one-definition-rule violations.

Enforcement
  • Flag non-conventional file names.
  • Check that .h and .cpp (and equivalents) follow the rules below.

SF.2: A .h file may not contain object definitions or non-inline function definitions

Reason

Including entities subject to the one-definition rule leads to linkage errors.

Example
???

Alternative formulation: A .h file must contain only:

  • #includes of other .h files (possibly with include guards)
  • templates
  • class definitions
  • function declarations
  • extern declarations
  • inline function definitions
  • constexpr definitions
  • const definitions
  • using alias definitions
  • ???
Enforcement

Check the positive list above.

SF.3: Use .h files for all declarations used in multiple source files

Reason

Maintainability. Readability.

Example, bad
// bar.cpp:
void bar() { cout << "bar\n"; }

// foo.cpp:
extern void bar();
void foo() { bar(); }

A maintainer of bar cannot find all declarations of bar if its type needs changing. The user of bar cannot know if the interface used is complete and correct. At best, error messages come (late) from the linker.

Enforcement
  • Flag declarations of entities in other source files not placed in a .h.

SF.4: Include .h files before other declarations in a file

Reason

Minimize context dependencies and increase readability.

Example
#include<vector>
#include<algorithm>
#include<string>

// ... my code here ...
Example, bad
#include<vector>

// ... my code here ...

#include<algorithm>
#include<string>
Note

This applies to both .h and .cpp files.

Exception: Are there any in good code?

Enforcement

Easy.

SF.5: A .cpp file must include the .h file(s) that defines its interface

Reason

This enables the compiler to do an early consistency check.

Example, bad
// foo.h:
void foo(int);
int bar(long double);
int foobar(int);

// foo.cpp:
void foo(int) { /* ... */ }
int bar(double) { /* ... */ }
double foobar(int);

The errors will not be caught until link time for a program calling bar or foobar.

Example
// foo.h:
void foo(int);
int bar(long double);
int foobar(int);

// foo.cpp:
#include<foo.h>

void foo(int) { /* ... */ }
int bar(double) { /* ... */ }
double foobar(int);   // error: wrong return type

The return-type error for foobar is now caught immediately when foo.cpp is compiled. The argument-type error for bar cannot be caught until link time because of the possibility of overloading, but systematic use of .h files increases the likelihood that it is caught earlier by the programmer.

Enforcement

???

SF.6: Use using-directives for transition, for foundation libraries (such as std), or within a local scope

Reason

???

Example
???
Enforcement

???

SF.7: Don't put a using-directive in a header file

Reason

Doing so takes away an #includer's ability to effectively disambiguate and to use alternatives.

Example
???
Enforcement

???

SF.8: Use #include guards for all .h files

Reason

To avoid files being #included several times.

Example
// file foobar.h:
#ifndef FOOBAR_H
#define FOOBAR_H
// ... declarations ...
#endif // FOOBAR_H
Enforcement

Flag .h files without #include guards.

SF.9: Avoid cyclic dependencies among source files

Reason

Cycles complicates comprehension and slows down compilation. Complicates conversion to use language-supported modules (when they become available).

Note

Eliminate cycles; don't just break them with #include guards.

Example, bad
// file1.h:
#include "file2.h"

// file2.h:
#include "file3.h"

// file3.h:
#include "file1.h"
Enforcement

Flag all cycles.

SF.20: Use namespaces to express logical structure

Reason

???

Example
???
Enforcement

???

SF.21: Don't use an unnamed (anonymous) namespace in a header

Reason

It is almost always a bug to mention an unnamed namespace in a header file.

Example
???
Enforcement
  • Flag any use of an anonymous namespace in a header file.

SF.22: Use an unnamed (anonymous) namespace for all internal/nonexported entities

Reason

Nothing external can depend on an entity in a nested unnamed namespace. Consider putting every definition in an implementation source file in an unnamed namespace unless that is defining an "external/exported" entity.

Example

An API class and its members can't live in an unnamed namespace; but any "helper" class or function that is defined in an implementation source file should be at an unnamed namespace scope.

???
Enforcement
  • ???