Concepts / Organizing Source Files in C++ Projects

Organizing Source Files in C++ Projects

Header files are used to represent interfaces and to emphasize logical structure, telling a reader what a component offers.

  • Programming

Why a Program Is Split Across Files

A C++ program is usually spread across several files, and some of those files are headers. Before reading any code, a useful question to ask is what a header file is for. The source material answers this directly: header files are used to represent interfaces and to emphasize logical structure. In other words, a header tells the reader what a part of the program offers, while the details of how it works live somewhere else.

What do you think happens?

When someone opens a header file, what should they be able to learn about the code without reading the implementation?

Reveal answer
Answer:

They should be able to learn what the component offers: the names and signatures of the functions it provides.

The header describes the interface. Reading it gives the logical structure of the component, while the implementation file holds the definitions that do the actual work.

The rest of this article follows one small idea from start to finish. You will see what a header promises, how its matching source file fulfills that promise, and what kinds of code should stay out of the header so that it keeps describing the interface rather than the implementation.

Reading a Header Versus Reading a Source File

The source material draws a clear split between two roles. The header sits on the interface side and declares what is offered. The source file sits on the implementation side and defines the functions the header declares. Reading the header gives the logical structure of the component; reading the source gives the work itself.

Function namewhat callers seeFunction bodythe statements that do theworkParameter typeswhat callers must supplyPrivate detailshelpers and local decisionsReturn typewhat callers receive
What does the header expose to other code, and what stays hidden in the source file?

Notice what the header column contains: names, parameter types, and return types. These are the things another file needs in order to call the function. The source column contains what makes the function do its job. A reader of the header learns how to use the component without learning how it is built.

Declaring in a Header and Defining in a Source File

A header that declares functions needs someone to define them. Think of a declaration as a promise about what a function looks like, and a definition as the code that keeps that promise. The header makes the promise visible to everyone; the source file keeps it. The example below is a small two-file project written to illustrate this split. It is not taken from the source material.

cpp
Notes on the code
  • The header declares add: it gives the name, the two int parameters, and the int return type.
  • There is no body here, so the header states what add offers without saying how it works.
cpp
Notes on the code
  • The #include line brings the header's declaration into this file.
  • The definition repeats the same name, parameters, and return type, and then supplies the body.

The reader of geometry.h learns that add exists and how to call it. The reader of geometry.cpp learns that a + b is the work. Splitting the two this way is exactly what the source means by headers emphasizing logical structure: the header shows the component's shape, and the source file fills in the detail.

Including the Header in Its Own Source File

The source material gives a clear rule: the source file that implements the functions declared in a header should include that header, using the #include directive. This means geometry.cpp begins with #include "geometry.h", even though it is the file that defines add. The implementation file therefore carries the header's declarations with it.

The reason this matters is that the definitions the implementation file writes can be checked against those declarations. Because the definition of add is compiled in the same file that sees the declaration, the definition and the declaration are written side by side and must agree with each other.

Following the Two-File Example Step by Step

To trace the whole project, add a third file that uses add. This file, main.cpp, is also a generated illustration. It includes the header, not the implementation, because it only needs to know what add offers.

cpp
Output
5
Notes on the code
  • main.cpp reads only the header, so it learns the name and signature of add.
  • The call add(2, 3) matches the declaration in the header; the body that produces 5 lives in geometry.cpp.
geometry.hdeclares addgeometry.cppincludes header, definesaddCompilerchecks each fileLinkerconnects the calls to addProgramprints 5main.cppincludes header, calls addincluded byincluded bycompiledcompiledpassed onproduces
When the header declares add and geometry.cpp defines it, what happens to each file as it moves through the compiler and linker?

Read the diagram from top to bottom. The header is the shared description that both geometry.cpp and main.cpp include. The compiler checks geometry.cpp against the declaration it includes. The compiler then handles main.cpp, which relies on the same declaration. The linker is where the call in main.cpp is connected to the definition in geometry.cpp, so the final program gets its add function from the implementation file, not from the header.

Why Full Function Bodies Stay Out of Headers

The source states that non-inline function definitions should be avoided in header files. The distinction matters because a header describes the interface, and a non-inline definition places a full implementation inside the header. The source gives the rule without elaborating its reasoning, so the practical habit to take from it is to keep full non-inline function bodies in source files and to keep headers focused on declarations.

Consider what would happen if the body of add were written in geometry.h instead. A reader opening the header would now see the implementation, which blurs the line between what the component offers and how it works. The header would also no longer be a short statement of structure. Keeping the body out of the header preserves the separation the rest of this article has been tracing.

Mistakes Beginners Make with Headers

  • Common mistake

    Putting the full body of a regular function into the header

    What it looks like

    geometry.h contains int add(int a, int b) { return a + b; } and is included by several source files.

    Why it fails

    The source says non-inline function definitions should be avoided in header files, and a header that carries full implementations no longer describes only the interface.

    Do this instead

    Keep the declaration int add(int a, int b); in the header and move the body into geometry.cpp.

  • Common mistake

    Writing a definition in the source file without including its own header

    What it looks like

    geometry.cpp defines add but omits #include "geometry.h".

    Why it fails

    The source says the source file that implements the declared functions should include that header, so the definition is no longer checked against the declaration in the same file.

    Do this instead

    Start geometry.cpp with #include "geometry.h" before writing the definition.

  • Common mistake

    Treating the header as the place where the work happens

    Why it fails

    The source says the header tells the reader what a part of the program offers, while the implementation file holds the definitions.

    Do this instead

    When looking for how something works, open the source file; use the header to learn what is available.

Practice Tracing a Header Change

EASY

Suppose geometry.h declares int multiply(int a, int b); and geometry.cpp is meant to define it. Without writing the body, list what a reader of geometry.h should learn, and state which file should hold the body of multiply and why.

Hints
  • Compare the header to the interface column in the earlier diagram: names, parameter types, and return types.
  • The source says non-inline function bodies should stay out of headers, and that the implementation file should include its header.

Summary of Header and Source Roles

  1. 1
    Header files are used to represent interfaces and to emphasize logical structure, telling a reader what a component offers.
  2. 2
    The source file that implements the functions declared in a header should #include that header, so the definitions can be checked against the declarations.
  3. 3
    Non-inline function definitions should be avoided in header files, so headers should focus on declarations while full bodies stay in source files.
  4. 4
    Reading a header gives the logical structure, while the implementation file holds the definitions that do the work.

At a glance

  • Header files are used to represent interfaces and to emphasize logical structure, telling a reader what a component offers.
  • The source file that implements the functions declared in a header should #include that header, so the definitions can be checked against the declarations.
  • Non-inline function definitions should be avoided in header files, so headers should focus on declarations while full bodies stay in source files.
  • Reading a header gives the logical structure, while the implementation file holds the definitions that do the work.

More concepts starting with O