C++ Core Guidelines: Source Files

Contents[Show]

The organisation of source files is a topic which is quite seldom addressed in C++. With C++20 we will get modules, but until then we should distinguish between the implementation and the interface of our code.

 serial 160834 1280

The C++ Core Guidelines makes their point to 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 to source files. The first eleven rules deals with interface files (*.h-files) and implementation files (*.cpp-files) and the last three with namespaces.

Let me start with the rules to the interface and implementation files. Here are the first seven:

I will not write about each rule in full depth, but I want to make a readable story out of the first rules by just quoting the rule.

Okay, SF.1: Use a .cpp suffix for code files and .h for interface files if your project doesn’t already follow another convention talks about consistency. When you have a C++ project, header files should be called *.h and implementation files *.cpp. Convention beats this rule if you have already another policy in our project. 

Of course, I often saw other conventions for header and implementation files. Here are a few, I have in mind:

  • Header files:
    • *.h
    • *.hpp
    • *.hxx
  • Implementation files:
    • *.cpp
    • *.c
    • *.cc
    • *.cxx

I assume you know various other conventions.

If your header file contains an object definition or a definition of a non-inline function, your linker may complain. This is the reason for the second rule SF.2: A .h file may not contain object definitions or non-inline function definitions. To be more specific, we have the One Definition Rule in C++:

ODR

ODR stands for the One Definition Rule and says in the case of a function.

  • A function can have not more than one definition in any translation unit.
  • A function can have not 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 each definition has to be the same.

In modern compilers, the keyword inline is not about inlining functions any more. Modern compilers almost completely ignore it. The more or less use-case for inline is to mark functions for ODR correctness. In my opinion, the name inline is nowadays quite misleading. 

Let see what my linker has to say when I try to link a program breaking the one definition rule. The following code example has one header file header.h and two implementation files. The implementations file includes the header files and, therefore, break the one definition rule, because of two definitions of func exit.

// header.h

void func(){}

// impl.cpp

#include "header.h"

// main.cpp

#include "header.h"

int main(){}

The linker complains about the multiple definitions of func:

odr

The next two rules are evident from the readability and maintainability point of view: SF.3: Use .h files for all declarations used in multiple source files and SF.4: Include .h files before other declarations in a file.

The rule 5 is more interesting: SF.5: A .cpp file must include the .h file(s) that defines its interface. The interesting question is: What would happen if you don't include the *.h file into the *.cpp file and there is a mismatch between the interface file *.h and the implementation file *.cpp?.

Assume I had a bad day. I defined a function func which gets and int and returns an int.

 

// impl.cpp

// #include "impl.h" 

int func(int){
    return 5;
}

My mistake was that I declared 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 use 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 program main.cpp is compiled. This is too late.

linker

If I include the header impl.h in my impl.cpp file, I will get a compile-time error.

compiler

The next rules are about namespaces: SF.6: Use using namespace directives for transition, for foundation libraries (such as std), or within a local scope (only). Honestly, this rule is too weak for me. I'm against using namespaces 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 will not compile, because there is a name clash. This is not my main argument against using directive. My main argument is that the using directive hide the origin of the name and break the readability of the code.

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::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 schoolDayInSeconds= 2 * schoolWay + 6 * schoolHour + 4 * shortBreak + longBreak + homework;

  cout << "School day in seconds: " << schoolDayInSeconds.count() << endl;

  duration<double, ratio<3600>> schoolDayInHours = schoolDayInSeconds;
  duration<double, ratio<60>> schoolDayInMinutes = schoolDayInSeconds;
  duration<double, ratio<1, 1000>> schoolDayInMilliseconds = schoolDayInSeconds;

  cout << "School day in hours: " << schoolDayInHours.count() << endl;
  cout << "School day in minutes: " << schoolDayInMinutes.count() << endl;
  cout << "School day in milliseconds: " << schoolDayInMilliseconds.count() << endl;

  cout << endl;

}

Do you know by heart, which literal, function, or object was defined in which namespace? If not, looking for the definition of a name may become a challenge. This holds, in particular, true, if you are a novice.

Before I end this post, there is one import rule I have to mention: SF.7: Don’t write using namespace at global scope in a header file. Here is the rational:

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

  • When you use the header, you can not undo the using directive.
  • The danger of a name collision increases drastically.
  • A change of the included namespace may break your build because a new name was introduced.

What's next?

First, a few rules to the organisation of source files are left. Additionally, we will get modules with C++20. Let's see which effect this significant features has on C++-

 

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Dilettant, Marko, Ramesh Jangama, G Prvulovic, Reiner Eiteljörge, Benjamin Huth, Reinhold Dröge, Timo, and Abernitzke.

 

Thanks in particular to:  TakeUpCode 450 60     crp4

 

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 600 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations
 

Cranepay

 

Comments   

0 #1 Jens Riboe 2019-04-22 13:00
Personally, I strongly disagree with the rule of not do "using namespace xyz".

Using a modern IDE, one always hover with the cursor, CTRL-SHIFT-I or something similar to quickly find out.

Prefixing e.g. a type name like this:
com::ribomation::collections::List lst
just clutters the code and do not add to the readability.

The same holds for easy to recognize identifiers such as:
cout, endl, string and similar.

In addition; I prefer using the file extensions
*.hxx and *.cxx
Quote
0 #2 Rainer Grimm 2019-04-23 15:12
Quoting Jens Riboe:
Personally, I strongly disagree with the rule of not do "using namespace xyz".

Using a modern IDE, one always hover with the cursor, CTRL-SHIFT-I or something similar to quickly find out.


But in a class I and my participants don't use a sophisticated editor. We use kate, kwrite, or nedit++.

Quote:

Prefixing e.g. a type name like this:
com::ribomation::collections::List lst
just clutters the code and do not add to the readability.

The same holds for easy to recognize identifiers such as:
cout, endl, string and similar.
I had a similar opinion but it happens often in my classes that my participants have problems understanding the basic identifiers; therefore, I apply this rule.
Quote:

In addition; I prefer using the file extensions
*.hxx and *.cxx
I will add both to the post.
Quote

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 1207

All 2427252

Currently are 144 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments