Concepts / Separating Declarations From Definitions

Separating Declarations From Definitions

A declaration names a function and describes how to call it, such as its return type and parameters.

  • Programming

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?

  • The exact step-by-step logic of every function

  • What the component offers and the logical structure of its interface

  • The memory layout of every variable the program uses

  • Nothing, because headers only contain comments

Reveal answer
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.

mathutils.hinterface sideDeclarationswhat is offeredmathutils.cppimplementation sideDefinitionshow it works#includecontainscontainsimplements
Which file declares what the component offers, which file defines how it works, and how the source file's #include of the header ties the two together.

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.

Functiondeclarationsint add(int a, int b);Functiondefinitionsreturn a + b;Describes theinterfacewhat the component offersHolds the workhow it workstells the readerdoes the work
The header holds function declarations that describe what is offered, and the source file holds function definitions that contain the bodies doing 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.

cpp
Notes on the code
  • 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.
cpp
Walk through it
  1. 1
    Line 2 includes mathutils.h, so this file sees the declarations of add and multiply.
  2. 2
    Lines 4 to 6 define add. Its signature matches the declaration in the header, and its body returns the sum.
  3. 3
    Lines 8 to 10 define multiply in the same way, with its body returning the product.
Notes on the code
  • 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.
Worked example

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?

  1. 1
    Read the header

    mathutils.h shows two declarations, add and multiply. The reader learns that the component offers two functions, each taking two int values and returning an int.

  2. 2
    Notice the missing bodies

    The header contains no return statements. The reader has the logical structure without any implementation detail.

  3. 3
    Follow the #include

    mathutils.cpp begins with #include "mathutils.h", so the implementation file carries the header's declarations with it.

  4. 4
    Read the definition

    The body of add in mathutils.cpp is return a + b;. That is the work the header promised, and the signature must match the declaration.

Result

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.

cpp
Notes on the code
  • 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);.
Header with fullbodyint add(...) { return a +b; }Source file AHeader withdeclarationint add(int a, int b);Source file Aadd definitionreturn a + b;Source file BSource file B#include copies body#include copies body#include#includedefines
When a header with a full function body is included by two source files, the body travels with it into both files. With only a declaration in the header, the header stays an interface and the single definition stays in one source file.
  • Common mistake

    Putting a full function body in the header because it is convenient

    What it looks like

    Writing int add(int a, int b) { return a + b; } directly in mathutils.h

    Why it fails

    The source says non-inline function definitions should be avoided in header files, and a header should focus on declarations.

    Do this instead

    Keep 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 like

    mathutils.cpp defines add without #include "mathutils.h"

    Why it fails

    The 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 instead
    Start mathutils.cpp with #include "mathutils.h".
  • Common mistake

    Reading the source file to learn what a component offers

    Why it fails

    The header is where the logical structure is meant to be read. The implementation file holds the work.

    Do this instead

    Open 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

EASY

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.h is the part a reader can see without opening weather.cpp.
MEDIUM

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.

More concepts starting with S