serial 160834 1280

C++ Core Guidelines: Source Files

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

 serial 160834 1280

The C++ Core Guidelines make 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 deal with interface files (*.h-files) and implementation files (*.cpp-files), and the last three with namespaces.

Let me start with the rules for 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 about consistency. 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 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++:

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    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 must satisfy the requirement that each must be the same.

    In modern compilers, the keyword inline is not about inlining functions anymore. Modern compilers almost wholly 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 me see what my linker says 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 breaks 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 following 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.

    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 in 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 that gets an 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 the 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 following 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 directives. My main argument is that the using directive hides the name’s origin and breaks the code’s readability.

    #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 rationale:

    A using namespace at 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.
    • Changing the included namespace may break your build because a new name was introduced.

    What’s next?

    First, a few rules for the organization of source files are left. Additionally, we will get modules with C++20. Let’s see which effect these significant features have on C++-

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, Charles-Jianye Chen, and Keith Jeffery.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *