I was reading Bjarne Stroustrups principle and practice using C++ and encountered the following in chapter 8.3 about header files:
To ease consistency checking, we #include a header both in sourcefiles that use its declarations and in source files that providedefinitions for those declarations. That way, the compiler catcheserrors as soon as possible.
There are two questions that emerged from this:
- How including the header file into the implementation file actually lets the compiler catch errors quicker.
- Thinking about question 1. I started wondering about why including header files into implementation files was necessary besides the argument of the compiler being able to catch error quicker. I get why it is logical to import a header file into another translation unit to give access to its declarations and then let the linker connect these declarations to the implementations. But how is the connection between the implementation and declarations established by the linker? Does including the header file into the implementation file tell the linker that declaration x should be connected to definition x?
I then tested a bit around to see what would happen if I did not include the header into the implementation file:
main.cpp:
#include "test_utility.h"int main(){ utility::printString("hello");}
test_utility.h:
#ifndef TEST_UTILITY#define TEST_UTILITY#include <string>namespace utility{ void printString(const std::string& str);}#endif
test_utility.cpp:
#include <iostream>#include <string>//#include "test_utility.h"namespace utility{ void printString(const std::string& str) { std::cout << str << std::endl; }}
I expected a linker error from this code saying something about not finding the definition of printString()
. What surprised me is that the compiler (gcc Ubuntu 11.4.0-1ubuntu1~22.04) did not give any warnings, error and the code worked fine. So what is really the point of including headers into implementation files? Is it only to help the compiler catch errors quicker like Stroustrup said? It seems the linker is able to connect corresponding implementations to corresponding definitions without the header in the implementation file.
For example does:
#include <iostream>#include <string>//#include "test_utility.h"namespace utility{ void printString(const std::string& str, int x) { std::cout << str << std::endl; }}
still generate a linker error in my testing.
Here is the CmakeLists.txt file I used for the testing:
set(PROJECT_NAME chap8)cmake_minimum_required(VERSION 3.22)project(${PROJECT_NAME} VERSION 1.0)set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)set(TARGET_NAME test) add_executable(${TARGET_NAME} "${TARGET_NAME}.cpp" "test_utility.cpp")#Compiler flagstarget_compile_options(${TARGET_NAME} PRIVATE -std=c++20 -fexceptions -Wall -g)