Chapter 15

Source Files

Images

Cippi juggles with source files.

With C++20 we get modules, but until we have modules available, we should distinguish between the implementation and the interface of our code.

The guidelines make their point regarding source files quite clear: “Distinguish between declarations (used as interfaces) and definitions (used as implementations). Use header files to represent interfaces and to emphasize logical structure.” Consequently, there are more than ten rules for source files. Most of the rules are quite concise. The first rules focus on interface and implementation files, and the remaining rules address namespaces.

Interface and implementation files

Declarations or interfaces are typically in *.h files and definitions or implementations in *.cpp files.

SF.1

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

When you have a C++ project, header files should be called *.h and implementation files *.cpp. Convention beats this rule if you already have another policy in your project.

I have often seen other conventions for header and implementation files. Here are a few I have in mind:

  • Header files

    • *.h

    • *.hpp

    • *.hxx

    • *.inl

  • Implementation files

    • *.cpp

    • *.c

    • *.cc

    • *.cxx

SF.2

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

If your header file contains an object definition or a definition of a noninline function, your linker may complain. This complaint is the reason for this rule. To be more specific, C++ has the One Definition Rule.

One Definition Rule

ODR stands for One Definition Rule. Here is what it says in the case of a function:

  • A function cannot have more than one definition in any translation unit.

  • A function cannot have more than one definition in the program.

  • Inline functions with external linkage can be defined in more than one translation. The definitions have to satisfy the requirement that they are all the same.

In modern compilers, the keyword inline is quite misleading. Modern compilers almost completely ignore it. The typical use case for inline is to mark functions for ODR correctness.

Let’s see what my linker has to say when I try to link a program breaking the ODR. The following code example has one header file header.h and two implementation files. Each implementation file includes this header file and, therefore, breaks the ODR, because two definitions of func exist.

// header.h
 
void func(){}
// impl.cpp
 
#include "header.h"
// main.cpp
 
#include "header.h"
 
int main() {}

The linker complains in this concrete case about the multiple definitions of the func-tion func. See Figure 15.1.

Images

Figure 15.1 Multiple definitions of a function

SF.5

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

The interesting question is, What happens if you don’t include the *.h file in the *.cpp file and there is a mismatch between the interface file *.h and the implementation file *.cpp?

Assume I am having a bad day. I define a function func that should get an int and return an int.

// impl.cpp
 
// #include "impl.h" (1)
 
int func(int) {
   return 5;
}

My mistake is that I declare this function in the header file impl.h, getting an int but returning a std::string.

// impl.h
 
#include <string>
 
std::string func(int);

I include the header in the main program because I want to invoke this function there.

// main.cpp
 
#include "impl.h"
 
int main() {
 
    auto res = func(5);
 
}

The issue is that the error may be delayed until link time when main.cpp is compiled. See Figure 15.2. This error is too late.

Images

Figure 15.2 Linker error because of mismatch between function declaration and definition

When I include the header impl.h in my impl.cpp (1) file, I get a compile-time error. See Figure 15.3.

Images

Figure 15.3 Compiler error because of a mismatch between function declaration and definition

SF.8

Use #include guards for all .h files

By putting an include guard around your header file, the header file is included only once. The following is the small example from the C++ Core Guidelines.

// file foobar.h:
#ifndef LIBRARYNAME_FOOBAR_H
#define LIBRARYNAME_FOOBAR_H
// ... declarations ...
#endif // LIBRARYNAME_FOOBAR_H

There are two points to keep in mind.

  1. Give your guard a unique name. If you use a guard name more than once, it may exclude the inclusion of a header file.

  2. #pragma is nonstandard but is a widely supported preprocessor directive. This pragma means the following variation of the header foobar.h is not portable.

// file foobar.h:
#pragma once

// ... declarations ...

SF.9

Avoid cyclic dependencies among source files

First of all, what is a cyclic dependency among source files? Imagine you have the following source files.

// a.h
 
#ifndef LIBRARY_A_H
#define LIBRARY_A_H
#include "b.h"
 
class A {
  B b;
};

#endif // LIBRARY_A_H
// b.h
 
#ifndef LIBRARY_B_H
#define LIBRARY_B_H
#include "a.h"
 
class B {
  A a;
};
 
#endif // LIBRARY_B_H
// main.cpp
 
#include "a.h"
 
int main() {
 A myA;
}

The compilation of the program fails (see Figure 15.4).

Images

Figure 15.4 Cyclic dependencies among source files

The issue is that there is a circular dependency between the header files a.h and b.h. The problem manifests itself when myA is created in the main program. To create an object of type A, the compiler must figure out the size of an object of type B. To create an object of type B, the compiler must figure out the size of A. This is not possible if the respective members of type A and type B, a or b, are objects. The determination of the size would only be possible if a or b were pointers or references.

The straightforward fix is, therefore, to forward declare A in b.h or B in a.h. Depending on your platform, the size of the reference or pointer is 32 or 64 bits. Here is the modified header of a.h.

#ifndef LIBRARY_A_H
#define LIBRARY_A_H
 
class B;
 
class A {
  B* b;
  B& b2 = *b;
};
 
#endif // LIBRARY_A_H

The standard library header <iosfwd> holds forward declarations of the input/output library.

SF.10

Avoid dependencies on implicitly #included names

For example, the following program will compile with GCC 5.4 but will break with the Microsoft compiler 19.00.23506.

#include <iostream>
 
int main() {
 
  std::string s = "Hello World";
  std::cout << s;
 
}

I forgot to include a necessary header <string>. GCC 5.4 includes <string> with the header <iostream>. This automatic inclusion does not happen with the Microsoft compiler.

SF.11

Header files should be self-contained

This rule is concise but important. A self-contained header file can be included topmost in a translation unit. Self-contained means that the header does not depend on other headers that were included before. If you don’t follow this rule, a user of your header may be surprised by difficult-to-understand error messages. Sometimes the header seems to work, sometimes not. It just depends on which header was included before.

Namespaces

A namespace is a scope for identifiers. Identifiers can be names of types, functions, or variables.

SF.6

Use using namespace directives for transition, for foundation libraries (such as std), or within a local scope (only)

Honestly, I want to reformulate this rule: Don’t use namespace directives such as in the following example.

#include <cmath>
using namespace std;
 
int g(int x) {
    int sqrt = 7;
    // ...
    return sqrt(x); // error
}

The program does not compile because there is a name clash. This is not my main argument against a using directive. My main argument is that the using directive hides the origin of the name and breaks the readability of the code.

// namespaceDirective.cpp

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;
using namespace std::literals::chrono_literals;
 
int main() {
 
  cout << '\n';
 
  auto schoolHour = 45min;
 
  auto shortBreak = 300s;
  auto longBreak = 0.25h;
 
  auto schoolWay = 15min;
  auto homework = 2h;
 
  auto schoolDayInSec = 2 * schoolWay + 6 * schoolHour + 
                          4 * shortBreak + longBreak + homework;
 
  cout << "School day in seconds: " << schoolDayInSec.count() << endl;
 
  duration<double, ratio<3600>> schoolDayInHours = schoolDayInSec;
  duration<double, ratio<60>> schoolDayInMin = schoolDayInSec;
  duration<double, ratio<1, 1000>> schoolDayInMilli = schoolDayInSec;
 
  cout << "School day in hours: " << schoolDayInHours.count() << endl;
  cout << "School day in minutes: " << schoolDayInMin.count() << endl;
  cout << "School day in milliseconds: " 
       << schoolDayInMilli.count() << endl;
 
  cout << endl;
 
}

Do you know by heart which function or object was declared in which namespace? If not, looking for the definition may be a challenge. This is true in particular if you are a novice.

Only the built-in literals in this example, such as 45min or 300s, are self-explanatory. Here is the adequate program, which this time doesn’t use the using directive for std and std::chrono.

// namespaceDirectiveRemoved.cpp

#include <iostream>
#include <chrono>
 
using namespace std::literals::chrono_literals;
 
int main() {
 
  std::cout << std::endl;
 
  auto schoolHour = 45min;
 
  auto shortBreak = 300s;
  auto longBreak = 0.25h;
 
  auto schoolWay = 15min;
  auto homework = 2h;
 
  auto schoolDayInSec = 2 * schoolWay + 6 * schoolHour + 
                          4 * shortBreak + longBreak + homework;
 
  std::cout << "School day in seconds: " 
            << schoolDayInSec.count() << std::endl;
 
  std::chrono::duration<double, std::ratio<3600>> schoolDayInHours = 
     schoolDayInSec;
  std::chrono::duration<double, std::ratio<60>> schoolDayInMin = 
     schoolDayInSec;
  std::chrono::duration<double, std::ratio<1, 1000>> schoolDayInMilli = 
     schoolDayInSec;
 
  std::cout << "School day in hours: " 
       << schoolDayInHours.count() << std::endl;
  std::cout << "School day in minutes: " 
       << schoolDayInMin.count() << std::endl;
  std::cout << "School day in milliseconds: " 
       << schoolDayInMilli.count() << std::endl;
 
  std::cout << std::endl;
 
}

SF.7

Don’t write using namespace at global scope in a header file

Here is the rationale for this important rule.

A using namespace at global scope in the header injects names into every file that includes that header. This injection has a few bad consequences:

  • When you use the header, you cannot undo the using directive.

  • The possibility of a name collision increases drastically.

  • A change to the included namespace may break your compilation because a new name is introduced.

SF.20

Use namespaces to express logical structure

Obviously, we have namespaces in the C++ standard to express logical structure. Examples? Here are a few:

std
std::chrono
std::literals
std::literals::chrono_literals
std::filesystem
std::placeholders
 
std::view // C++20

SF.21

Don’t use an unnamed (anonymous) namespace in a header

and

SF.22

Use an unnamed (anonymous) namespace for all internal/ nonexported entities

An unnamed namespace has internal linkage. Internal linkage means that names inside the unnamed namespace can be referred only from within the current translation unit and are not exported. The same applies to names, which are declared in the unnamed namespace. Okay, what does that mean?

namespace {
   int i; // defines ::(unique_name)::i
}
void inc() {
   i++; // increments ::(unique_name)::i
}

When you refer to i from within the translation unit, you do so by an implicit unique_name that is specific to the current compilation unit, and therefore, there is no name clash. For example, you can define an add addition function inside the unnamed namespace, and the linker does not complain. In this case, you would not break the One Definition Rule even if your header is included more than once.

When you use an unnamed namespace in the header, each translation unit defines its unique instance of the unnamed namespace. Unnamed namespaces in headers have a few consequences:

  • The resulting executable size bloats.

  • Any declaration in an unnamed namespace refers to a different entity in each translation unit. This may not be the expected behavior.

The usage of an unnamed namespace is similar to the static keyword used in C.

namespace { int i1; }
static int i2;