SovranCode
Modern C++ Lesson 46
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Performance: Benchmark Before Optimizing
NEXT LESSONC++ CMake Tutorial: Build an Executable
C++ foundations · Lesson 46 About 51 min with practice

C++ Header Files and Translation Units

Learn C++ header and source files with a small real project: declarations, definitions, include guards, translation units, object files and clear compiler-versus-linker troubleshooting.

Before you begin

Know functions, structs, optional, input validation and compile/link stages. The in-page runner accepts one complete source file; multi-file builds use the local project instructions.

What you will learn

  • Separate an operation's declaration from its definition.
  • Write a self-contained guarded header.
  • Compile source files into separate translation units.
  • Link the needed object files exactly once.
  • Diagnose a missing definition without including implementation files.

Share what callers need without copying all implementation code

A small program can fit in main.cpp. As it grows, you may want the input code in one file and the reading calculation in another. The caller still needs to know the function's name, parameter types and return type. A header shares those declarations. A source file provides the ordinary function's definition, which is its implementation body. Keeping those roles distinct makes separate compilation easier to explain and maintain.

A declaration introduces what can be used. A definition supplies the actual object or function being defined. A function declaration ends with a semicolon; its definition includes the body. In our project, report.hpp declares ReadingPair and totalOf. report.cpp defines totalOf. main.cpp includes the header and calls the function. It does not include report.cpp as a shortcut to finding the implementation.

The preprocessor expands includes before the compiler translates a source file. A translation unit is, roughly, one source file after its included content and preprocessing have been handled. report.cpp and main.cpp therefore become separate translation units that both know the shared declarations. The linker later connects the compiled references with the needed definitions. An editor showing a declaration is not proof that the build has linked its implementation.

report.hpp supplies declarations to report.cpp and main.cpp. Each source file compiles into its own object file, report.o and main.o. The linker combines both objects into one program; the header is not shown as a third independently linked source file.
Headers share declarations through includes; source files compile separately and their object files are linked together. Include guards prevent repeated header contents inside one translation unit, not duplicate ordinary definitions across the whole program.
PROJECT FILE · LOCAL BUILD

report.hpp

report.hpp

Save this project file beside the other listed files. It is not a complete standalone lesson-runner program.

#ifndef SOVRA_REPORT_HPP
#define SOVRA_REPORT_HPP
#include <optional>
namespace course {
struct ReadingPair { int first; int second; };
std::optional<int> totalOf(const ReadingPair& pair);
}
#endif
PROJECT FILE · LOCAL BUILD

report.cpp

report.cpp

Save this project file beside the other listed files. It is not a complete standalone lesson-runner program.

#include "report.hpp"
namespace course {
std::optional<int> totalOf(const ReadingPair& pair) {
    if (pair.first < -100 || pair.first > 100 || pair.second < -100 || pair.second > 100)
        return std::nullopt;
    return pair.first + pair.second;
}
}
PROJECT FILE · LOCAL BUILD

main.cpp

main.cpp

Save this project file beside the other listed files. It is not a complete standalone lesson-runner program.

#include "report.hpp"
#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
int main() {
    course::ReadingPair pair{};
    if (!readInteger(pair.first) || !readInteger(pair.second)) {
        std::cout << "Invalid input\n"; return 1;
    }
    const auto total = course::totalOf(pair);
    if (!total) { std::cout << "Rejected\n"; return 2; }
    std::cout << "Total: " << *total << '\n';
}

Follow one declaration from header to executable

The header includes optional because its own public declaration uses std::optional<int>. A header should include what it directly needs rather than accidentally depending on some earlier include in the caller. ReadingPair owns two integers, and totalOf accepts it by const reference. The declaration tells the caller enough to type-check a call without copying the implementation body into main.cpp.

The definition checks each integer against minus one hundred through one hundred before adding them. The sum is then within minus two hundred through two hundred. An out-of-range pair returns no value. main.cpp parses two whole integer lines, distinguishes malformed input from a rejected pair, and prints a total only for a present result. A total of zero remains a valid present answer, as in earlier optional lessons.

Compile both source files, then link both resulting objects. The command sequence below makes those stages visible. A one-command build listing both sources is also valid for this small project. A later lesson introduces CMake so the build can record sources and requirements without relying on someone remembering every command. First understand what the build tool will need to automate.

  • main.cpp includes report.hpp and learns the function declaration.
  • report.cpp includes the same header and supplies the matching definition.
  • Each source compiles into its own object file.
  • The linker combines both objects, then the executable handles the input.
LOCAL TERMINAL COMMANDS

Build the three-file project locally

Save the listed files together in a new practice folder. Run from that folder. For sample input 10 and -3 on separate lines, a successful executable prints Total: 7. Do not paste this multi-file command into the single-file lesson source editor.

Clang on macOS / Linux (or g++ with equivalent flags):
clang++ -std=c++20 -Wall -Wextra -Wpedantic -c report.cpp -o report.o
clang++ -std=c++20 -Wall -Wextra -Wpedantic -c main.cpp -o main.o
clang++ main.o report.o -o report
./report

Equivalent single compiler-driver invocation:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp report.cpp -o report

MSVC Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp report.cpp /Fe:report.exe
report.exe

A guard stops repeated inclusion, not every duplicate in a program

The header's ifndef, define and endif form an include guard. Once this header's guarded contents have been included in one translation unit, including it again there does not repeat those contents. Use a project-specific guard name to reduce accidental collisions. Some compilers also support pragma once, but the guard shown here uses standard preprocessing facilities and makes the purpose visible to a student.

An include guard does not prevent an ordinary non-inline function definition in a header from appearing in several different translation units. Each source is preprocessed separately. If several resulting object files supply the same ordinary definition, the program violates the relevant one-definition rules and commonly gets a duplicate-symbol linker error. For this project, keep the ordinary totalOf body in report.cpp exactly once.

Templates are an important related case. Their definitions are commonly placed in headers so the compiler can instantiate the needed uses. Inline functions and suitable inline variables also have specific rules that allow matching definitions across translation units. Do not simplify this into either all definitions belong in cpp files or everything can be placed in a guarded header. The right location depends on what kind of entity you are defining.

C++ PRACTICE RUNNER

Practise the same calculation in one complete file

This runnable counterpart combines declaration and definition for the single-file editor. It does not pretend to test separate compilation.

#include <iostream>
#include <optional>
namespace course {
struct ReadingPair { int first; int second; };
std::optional<int> totalOf(const ReadingPair& pair);
std::optional<int> totalOf(const ReadingPair& pair) {
    if (pair.first < -100 || pair.first > 100 || pair.second < -100 || pair.second > 100)
        return std::nullopt;
    return pair.first + pair.second;
}
}
int main() {
    const auto total = course::totalOf({10, -3});
    if (total) std::cout << "Total: " << *total << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Total: 7

After editing inputs, calculate a new prediction; this reference describes only the starter.

C++ PRACTICE RUNNER

Repair a missing declaration before blaming the linker

Intentional compile error. Add a matching declaration before main, or move a suitable definition above its first call.

int main() { return totalOf(2, 3); }
int totalOf(int first, int second) { return first + second; }

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

C++ PRACTICE RUNNER

Keep the declared and called namespace consistent

The namespace is part of the function's identity. This complete example uses an unambiguous qualified call.

#include <iostream>
namespace course {
int addSmall(int first, int second) { return first + second; }
}
int main() { std::cout << course::addSmall(2, 3) << '\n'; }

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
5

After editing inputs, calculate a new prediction; this reference describes only the starter.

A compiler failure and a linker failure ask different questions

An undeclared name means the compiler cannot see a suitable declaration at the call. A missing header points to the include path or file location. A matching declaration with no linked definition can compile to an object but fail at linking with an unresolved reference or similar message. Read which stage failed before making changes. Adding a cast or moving an output statement does not supply a missing object file to the linker.

A duplicate-definition error can happen when you compile a source file twice or include its implementation and also compile it separately. Repair the build or definition placement instead of deleting random object files until one command appears to work. The project deliberately includes only report.hpp in its sources. Keep the function signature and namespace identical between declaration and definition; a look-alike function in another namespace does not satisfy the caller's reference.

Rebuild all affected sources after changing a shared declaration, and launch the newly named output only after a successful link. A stale executable may remain after a failed build. A clean build from the saved project files is useful evidence that you did not rely on an old object by accident. Avoid putting using namespace std in a shared header because it affects every source that includes it and can create surprising name conflicts.

Validate behavior, then prove the split project builds

Implement the two-reading contract shown in the project. The single-file reference solution reads two complete integers, prints Invalid input with status one for malformed or missing input, and prints Rejected with status two when either number is outside minus one hundred through one hundred. Otherwise print Total with status zero, including a valid zero sum. Validate before adding so extreme int input cannot overflow the operation.

Then split the logic into report.hpp, report.cpp and main.cpp using the supplied files, build from a clean practice directory and compare the same input cases. Try omitting report.o from the link to observe a missing definition, then restore it. Include the header twice to verify its guard. Record source names, compiler commands and actual results. A successful single-file run proves the calculation, not the separate project organization, so keep both pieces of evidence.

LOCAL TERMINAL COMMANDS

Build one complete safe example

Choose an installed compiler. These commands run locally, not in the source editor. The in-page Run button requires the configured compiler sandbox. Diagnostic fixtures and multi-file projects use their separate instructions.

macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Build a checked calculation as both a complete single-file exercise and a real guarded three-file project.

Acceptance checklist

  • Read two whole signed integers; reject malformed or missing input as Invalid input with status 1.
  • Validate each in -100..100 before addition; print Rejected with status 2 for a well-formed out-of-domain pair.
  • Return optional total so a present zero is not confused with rejection; print Total with status 0 on success.
  • Put declarations and their direct includes in guarded report.hpp, one ordinary definition in report.cpp, and the caller in main.cpp.
  • Compile and link the actual project, verify repeated header inclusion, and distinguish an omitted-object link error from a missing-declaration compiler error.
Need a hint?
  1. The in-page runner accepts one complete file, not an include of files that exist only in the lesson text.
  2. Include the header in each source; do not include report.cpp.
  3. The namespace and function signature must match between declaration, definition and call.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <optional>
#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
namespace course {
struct ReadingPair { int first; int second; };
std::optional<int> totalOf(const ReadingPair& pair);
std::optional<int> totalOf(const ReadingPair& pair) {
    if (pair.first < -100 || pair.first > 100 || pair.second < -100 || pair.second > 100)
        return std::nullopt;
    return pair.first + pair.second;
}
}
int main() {
    course::ReadingPair pair{};
    if (!readInteger(pair.first) || !readInteger(pair.second)) {
        std::cout << "Invalid input\n"; return 1;
    }
    const auto total = course::totalOf(pair);
    if (!total) { std::cout << "Rejected\n"; return 2; }
    std::cout << "Total: " << *total << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Total: 7

The complete reference source verifies the domain behavior in the single-file editor. The project blocks provide the equivalent separated sources for local compilation.

Each source sees the declaration through the guarded header, while exactly one ordinary definition is linked from report.cpp.

The accepted numeric range bounds the sum before arithmetic. Compiler, linker and runtime evidence remain separate checks rather than one generic success claim.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Separate an operation's declaration from its definition.
  • Write a self-contained guarded header.
  • Compile source files into separate translation units.
  • Link the needed object files exactly once.
  • Diagnose a missing definition without including implementation files.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • C++ draft: translation units
  • C++ draft: source inclusion
  • C++ draft: one-definition rule
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What belongs in this project's header?
02Does an include guard prevent every cross-file duplicate definition?
03main.o references totalOf but report.o is omitted. Which stage commonly fails?
04Should main.cpp include report.cpp in this project?
05Does the single-file counterpart prove the split project builds?
PREVIOUS LESSONC++ Performance: Benchmark Before Optimizing
NEXT LESSONC++ CMake Tutorial: Build an Executable
ON THIS PAGELearning objectivesShare what callers need without copying all implementation codeFollow one declaration from header to executableA guard stops repeated inclusion, not every duplicate in a programA compiler failure and a linker failure ask different questionsValidate behavior, then prove the split project buildsIndependent labReviewKnowledge checkReferences
Course overview