C++ PROGRAMMING ยท 22 MIN STUDY

Declarations, definitions, and header files

Split a C++ program into understandable files, distinguish declarations from definitions, and understand the One Definition Rule at a practical level.

Learning objectives

  • Distinguish a declaration from a definition.
  • Explain the role of header and source files.
  • Compile and link a small multi-file program.
  • Describe header guards and the One Definition Rule (ODR).

Prerequisites: function declarations and definitions, source files, and basic compiler use. Review Functions and Compiler toolchain first.

Declaration versus definition

A declaration tells the compiler that a name and its type exist. A definition provides the entity itself, such as a function body or the storage for a variable. A function prototype is a declaration; a function body is a definition.

int multiply(int left, int right); // declaration

int multiply(int left, int right) { // definition
    return left * right;
}

The declaration lets code call the function before the compiler has seen its definition. The linker later connects the call to the compiled definition.

Why split files?

As programs grow, putting every function in one file becomes difficult to maintain. A common layout is:

project/
  main.cpp
  math_utils.h
  math_utils.cpp

The header (.h or .hpp) contains declarations that other source files need. The source file (.cpp) contains the function definitions. The main file includes the header and calls the function.

A small working example

math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int multiply(int left, int right);

#endif

math_utils.cpp

#include "math_utils.h"

int multiply(int left, int right) {
    return left * right;
}

main.cpp

#include <iostream>
#include "math_utils.h"

int main() {
    std::cout << multiply(6, 7) << '\n';
}

Compile both source files together, for example with GCC or Clang:

g++ -std=c++17 -Wall -Wextra -Wpedantic main.cpp math_utils.cpp -o app
./app

Expected output: 42. On Windows with MinGW, run app.exe. In an IDE, add both .cpp files to the same project; do not compile the header as a separate source file.

What happens during the build?

  1. The preprocessor handles #include by making header declarations available to the source file.
  2. The compiler compiles each .cpp source file as a translation unit and produces object code.
  3. The linker combines object files and libraries, resolving references such as the call to multiply.

A translation unit is, roughly, one source file after preprocessing. Each .cpp file is compiled independently, which is why declarations must be visible where a name is used.

Header guards and duplicate inclusion

The #ifndef, #define, and #endif lines form a header guard. They prevent the same header's contents from being processed more than once within one translation unit. Many compilers also support #pragma once, but include guards are the portable traditional form.

The One Definition Rule (ODR)

The ODR is a set of rules that limit how many definitions an entity may have across a program. A common beginner-safe design is: declare an ordinary non-inline function in a header and define it once in one .cpp file. If you put the same non-inline function definition in a header included by multiple source files, you can create multiple definitions and a linker error.

Some entities, including appropriately defined templates and inline functions, are designed to have definitions in headers under specific ODR rules. Learn those cases separately; do not mark every function inline just to silence a linker error.

Common mistakes

  • Declaring a function in a header but forgetting to compile/link its source definition.
  • Including a header without include guards and causing repeated declarations or definitions.
  • Defining a non-inline ordinary function in a header included by multiple source files.
  • Using angle brackets for your own project header; use quotes for local headers such as #include "math_utils.h".
  • Trying to compile a header by itself instead of compiling the relevant source files.

Practice exercises

  1. Write a declaration for double area(double radius) and a definition that returns pi times radius squared.
  2. Which file should contain the function body in the three-file layout above?
  3. What is the purpose of the #ifndef header guard?
  4. Predict the likely problem if the same non-inline function body is placed in a header included by main.cpp and helpers.cpp.

Solutions

Show exercise solutions
  1. Declaration: double area(double radius);. Definition: double area(double radius) { return 3.141592653589793 * radius * radius; }.
  2. Put the body in math_utils.cpp; put the declaration in math_utils.h.
  3. It ensures the header's contents are included at most once per translation unit, avoiding repeated processing.
  4. Each source file may emit a definition, so the linker can report a multiple-definition error. Keep one definition in a single source file or use a valid inline/template header design.

Summary

Headers publish declarations; source files commonly hold definitions. Each source file is compiled separately, then the linker combines the results. Header guards prevent repeated inclusion in a translation unit. Follow ODR rules to avoid duplicate definitions.

References