Separating Declarations From Definitions
A declaration names a function and describes how to call it, such as its return type and parameters.
Opening a Header Without the Implementation
A C++ program is usually spread across several files, and some of those files are headers. Before reading any code, it helps to ask what a header file is for. The source material answers this directly: header files are used to represent interfaces and to emphasize logical structure. A header tells the reader what a part of the program offers, while the details of how it works live elsewhere.
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
What the component offers and the logical structure of its interface.
Reading the header gives the logical structure. The implementation file holds the definitions, which is where the work itself is done.
The rest of this article follows that question. It starts by looking at the two kinds of file that make up a small program, then separates declarations from definitions, then shows how a header connects to the file that implements it, and finally identifies which code should stay out of headers.
Two Files, Two Jobs
The header sits on the interface side of a program. It declares what is offered. The source file sits on the implementation side. It defines the functions that the header declares. Reading the header gives you the logical structure of the component. Reading the source file gives you the work itself.
Notice the direction of the #include. The implementation file pulls in the header, not the other way around. That is the connection the next sections examine in detail.
Declarations Versus Definitions
A declaration names a function and describes how to call it, such as its return type and parameters. A definition gives the function its body, the code that actually runs. The source material places declarations in the header and definitions in the source file, because a header describes the interface while the implementation file holds the work.
Including the Header in Its Own Source File
A header that declares functions needs someone to define them. 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. Because of this, the implementation file carries the header's declarations with it, and the definitions it writes can be checked against those declarations.
- The header lists only declarations: return type, name, and parameters.
- No function bodies appear here, so a reader learns what is offered without reading the implementation.
- 1Line 2 includes
mathutils.h, so this file sees the declarations of add and multiply. - 2Lines 4 to 6 define add. Its signature matches the declaration in the header, and its body returns the sum.
- 3Lines 8 to 10 define multiply in the same way, with its body returning the product.
- The #include line brings in the header's declarations.
- Each definition must match a declaration from the header, so the compiler can check it against the interface.
- The bodies live here, in the implementation file.
Tracing the two files
A reader opens mathutils.h and wants to know what the component offers. Then the reader opens mathutils.cpp to see how add works. What does each file tell them?
- 1Read the header
mathutils.hshows two declarations, add and multiply. The reader learns that the component offers two functions, each taking two int values and returning an int. - 2Notice the missing bodies
The header contains no return statements. The reader has the logical structure without any implementation detail.
- 3Follow the #include
mathutils.cppbegins with #include "mathutils.h", so the implementation file carries the header's declarations with it. - 4Read the definition
The body of add in
mathutils.cppis return a + b;. That is the work the header promised, and the signature must match the declaration.
The header tells the reader what is offered. The implementation file, connected through #include, shows how add does its work.
Which Definitions Stay Out of Headers
The third rule in this section concerns what goes inside a header. The source states that non-inline function definitions should be avoided in header files. 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 is clear: keep full non-inline function bodies in source files, and keep headers focused on declarations.
- The full non-inline body of add now sits in the header.
- Compare with the recommended header, which keeps only the declaration int
add(int a, int b);.
Common mistake Putting a full function body in the header because it is convenient
What it looks likeWriting int add(int a, int b) { return a + b; } directly in mathutils.h
Why it failsThe source says non-inline function definitions should be avoided in header files, and a header should focus on declarations.
Do this insteadKeep the declaration int add(int a, int b); in the header and place the body in mathutils.cpp.
Common mistake Writing a source file without including its own header
What it looks likemathutils.cpp defines add without #include "mathutils.h"
Why it failsThe source says the implementation file that defines the declared functions should include that header, which is what lets the definitions be checked against the declarations.
Do this insteadStart mathutils.cpp with #include "mathutils.h".Common mistake Reading the source file to learn what a component offers
Why it failsThe header is where the logical structure is meant to be read. The implementation file holds the work.
Do this insteadOpen the header first to learn what is offered, then open the source file only when you need to see how it works.
Tracing a Small Project
A project has two files. The header weather.h declares the function double celsius_to_fahrenheit(double c);. The file weather.cpp includes weather.h and defines that function. A reader wants to know whether the function is offered to other files. Which file answers this, and which file should the reader open to see the conversion formula?
Hints
- Ask which file the source describes as representing interfaces.
- Ask which file holds the definitions that do the work.
- The declaration in
weather.his the part a reader can see without openingweather.cpp.
A teammate moves the full body of celsius_to_fahrenheit from weather.cpp into weather.h so that the header is self-contained. Using the rule from this article, explain what is wrong with this change and describe how you would restructure the two files.
Hints
- Compare what the header is meant to describe with what the body contains.
- The source says non-inline function definitions should be avoided in header files.
- Keep the declaration in the header and return the body to the source file.
Key Takeaways for Header Design
A header file represents an interface. Reading it tells you what a component offers, and the source file is where the definitions that do the work live.
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 its definitions can be checked against the declarations.
- Non-inline function definitions should be avoided in header files; keep full bodies in source files and keep headers focused on declarations.
- Reading a header gives the logical structure, while the implementation file holds the definitions.