Every example so far has lived in a single file. That's perfect for learning, but real programs have thousands of lines. Keeping everything in main.cpp makes it hard to find anything, slow to compile, and impossible for several people to work on without constantly editing the same file.
C++ projects are split into many files, usually in pairs: a header file (.h or .hpp) that declares what a piece of code offers, and a source file (.cpp) that defines how it works. Other files include the header to use the code without needing to see its inner workings.
In this lesson you'll split a program into headers and source files, stop headers being included twice, group related code into namespaces, compile and link several files, understand the linker errors that confuse almost every beginner, and build the project with CMake, the tool most C++ projects use.
Declarations and definitions
A declaration tells the compiler that something exists and what it looks like. A definition provides the actual body:
double areaOfCircle(double r); // declaration
double areaOfCircle(double r) { // definition
return 3.14159 * r * r;
}A function can be declared many times, but defined only once in the whole program. This is the one definition rule. Headers hold declarations, such as function prototypes and class definitions with their member declarations, while .cpp files hold the function bodies. Small functions written inside a class body, and templates, are exceptions that live in headers.
Header and source files
For a module called geometry:
geometry.hdeclares the functions or class.geometry.cppincludesgeometry.hand defines everything.main.cppincludesgeometry.hand uses it.
Use quotes for your own headers, #include "geometry.h", and angle brackets for standard and installed libraries, #include <vector>. #include simply pastes the file's text in place, which is why a header must never contain function bodies that aren't marked inline, and why headers need protection against being included twice.
#pragma once, guards and namespaces
If two headers both include geometry.h, its contents could appear twice in one file and cause "redefinition" errors. Put this at the top of every header:
#pragma onceOlder code uses include guards with #ifndef GEOMETRY_H, #define GEOMETRY_H and a final #endif, which do the same job.
A namespace groups names to avoid clashes between libraries: namespace geo { double area(double r); } is used as geo::area(2). Never put using namespace std; in a header, because it would affect every file that includes it.
Compiling, linking and CMake
Building happens in two stages. The compiler turns each .cpp file into an object file separately, and the linker joins them into one program:
g++ -std=c++17 -Wall main.cpp geometry.cpp -o appCMake describes the project once in a CMakeLists.txt file, then generates the right build commands for any compiler and operating system. Editors such as VS Code, CLion and Visual Studio open CMake projects directly, and it's the standard way C++ libraries describe how to build them.
C++ Projects Syntax
Project layout
project/
CMakeLists.txt
include/geometry.h #pragma once, declarations
src/geometry.cpp #include "geometry.h", definitions
src/main.cpp #include "geometry.h", main()g++ -std=c++17 -Iinclude src/*.cpp -o app
cmake -S . -B build && cmake --build buildC++ Projects Summary
- Headers declare what code offers; source files define how it works.
- Every function may be declared many times but defined only once (the one definition rule).
- Start every header with #pragma once (or include guards); use quotes for your own headers.
- Namespaces group names, like geo::area; never put using namespace in a header.
- The compiler builds each .cpp file, the linker joins them, and CMake describes the whole build.
C++ Projects Examples
Each example shows the code, the output it produces and what each part does.
A module in two files
// geometry.h
#pragma once
namespace geo {
const double PI = 3.14159265;
double circleArea(double radius);
double rectangleArea(double width, double height);
}// geometry.cpp
#include "geometry.h"
namespace geo {
double circleArea(double radius) {
return PI * radius * radius;
}
double rectangleArea(double width, double height) {
return width * height;
}
}geometry.h declares what the geo module offers.
geometry.cpp is compiled once and provides the bodies.Line-by-line explanation
- Line 2In geometry.h, #pragma once stops the header being pasted twice into the same file.
- Line 4Everything is inside the geo namespace, so the names can't clash with other code.
- Lines 7 to 8Only declarations: names, parameters and return types, ending with semicolons.
Using the module and building it
// main.cpp
#include <iostream>
#include "geometry.h"
using namespace std;
int main() {
cout << "Circle r=2: " << geo::circleArea(2) << endl;
cout << "Room 4x3.5: " << geo::rectangleArea(4, 3.5) << endl;
return 0;
}$ g++ -std=c++17 -Wall main.cpp geometry.cpp -o app
$ ./app
Circle r=2: 12.5664
Room 4x3.5: 14Line-by-line explanation
- Line 3Quotes look for the header in your project folder first.
- Line 4using namespace std is fine in a .cpp file, never in a header.
- Lines 7 to 8geo:: picks the functions from the geo namespace. Both .cpp files are listed in the build command; leaving out geometry.cpp would cause an "undefined reference" linker error.
A CMakeLists.txt for the project
cmake_minimum_required(VERSION 3.16)
project(ShapeApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(app
src/main.cpp
src/geometry.cpp
)
target_include_directories(app PRIVATE include)$ cmake -S . -B build
-- Configuring done
-- Generating done
-- Build files have been written to: build
$ cmake --build build
[ 33%] Building CXX object CMakeFiles/app.dir/src/main.cpp.o
[ 66%] Building CXX object CMakeFiles/app.dir/src/geometry.cpp.o
[100%] Linking CXX executable appLine-by-line explanation
- Lines 1 to 2The minimum CMake version and the project's name.
- Lines 4 to 5Compile everything as C++17.
- Lines 7 to 10One program, called app, built from two source files.
- Line 11Tell the compiler where to find the headers.
Common C++ Projects Mistakes
A function body in a header
// helpers.h, included by two .cpp files
int twice(int x) { return x * 2; } // multiple definition error// helpers.h
int twice(int x);
// helpers.cpp
int twice(int x) { return x * 2; }A header without protection
// shapes.h
struct Point { int x, y; }; // redefinition if included twice// shapes.h
#pragma once
struct Point { int x, y; };using namespace std in a header
// utils.h
#pragma once
#include <string>
using namespace std; // leaks into every file that includes it// utils.h
#pragma once
#include <string>
std::string shout(const std::string& s);C++ Projects in Real Projects
How real projects are organised
Large C++ projects such as game engines, browsers and databases contain thousands of header and source pairs, grouped into folders and namespaces by feature. Build systems like CMake compile only the files that changed, which keeps builds fast. Libraries ship their headers in an include folder so other projects can use them, exactly like the standard library's own headers.
C++ Projects FAQ
What is the difference between .h and .hpp files?
Nothing, as far as the compiler is concerned: #include simply pastes in the file, whatever its extension. .h is traditional and shared with C, while some projects use .hpp to show the header is C++ only. Pick one and use it consistently across a project.
How do I share a global variable between files?
Declare it in a header with extern, which says "this exists somewhere": extern int playerCount;. Then define it, without extern, in exactly one .cpp file: int playerCount = 0;. Since C++17 you can instead write inline int playerCount = 0; in the header. Better still, avoid shared globals and pass values as parameters.
How do I keep a helper function private to one .cpp file?
Put it in an unnamed namespace at the top of that .cpp file: namespace { int helper() { ... } }. The function can be used anywhere in that file but is invisible to the linker from other files, so another file can have its own helper without a "multiple definition" error. Older code gets the same effect by marking the function static.
What is a forward declaration and when should I use one?
A forward declaration such as class Engine; tells the compiler a class exists without including its header. It's enough when a header only uses pointers or references to that class, as in Engine* engine;. Using forward declarations instead of #include in headers cuts down how much code gets recompiled when one header changes.
Do C++20 modules replace header files?
They're designed to: a module file starts with export module geo;, and other files write import geo; instead of #include. Modules compile faster and don't leak macros, but compiler and build-tool support is still uneven; g++ 13, for example, needs the -fmodules-ts flag. Most existing projects and tutorials still use headers, so learn those first.
C++ Projects Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the first line of a header so it can't be included twice.
#pragma
double circleArea(double radius);
Where do function bodies usually go?
What does "undefined reference to geo::circleArea(double)" usually mean?
Which include style is for your own project headers?
Why should a header never contain using namespace std;?
C++ Projects Quiz
5 questions to check what you learned.
What is a declaration?
- The body of a function
- A statement that something exists, with its name and type
- A comment
- A compiled file
What does the one definition rule say?
- Each file may have one function
- Each class may have one constructor
- Each project may have one header
- Each function or variable may be defined only once in the whole program
What does the linker do?
- Checks spelling
- Downloads libraries
- Joins compiled object files into one program
- Runs the program
How do you call
areainsidenamespace geofrom main?- area()
- geo.area()
- namespace.area()
- geo::area()
What's the main job of CMake?
- Compiling C++ itself
- Formatting code
- Describing the project once and generating build commands for any compiler and system
- Running unit tests only