Why projects grow beyond one file
A first program fits in main.cpp. As an application grows, keeping every function in one file makes it harder to find, review and reuse code. A small project can place the entry point in one .cpp file and related functions in another, with a header that tells other source files what functions are available. This is organisation, not a requirement that every function gets its own file.
The example below has a greeting function and an entry point. Both .cpp files are compiled, and their object files are linked into one executable. This extends the single-file walkthrough; read that first if #include or main is unfamiliar.
A small, readable folder layout
Keep editable source under src/, shared headers under include/ and generated build output under build/. CMakeLists.txt is optional if you compile manually, but it is included here to show where a build description usually lives. Folder names are conventions, not C++ syntax. Do not hand-edit object files or executables in build/.
main.cpp starts the program, greeting.cpp defines the greeting function, and greeting.hpp declares that function. The .hpp extension commonly marks a C++ header; .h is also common and is not reserved for C. The project must tell its compiler where include/ is and which .cpp files to compile.
Project tree
hello-project/
CMakeLists.txt
include/
greeting.hpp
src/
main.cpp
greeting.cpp
build/ generated files after buildingDeclare an interface in a header
Save the first code block as include/greeting.hpp. The declaration const char* greeting(); states the function’s name, parameters and return type without providing its body. Any source file that includes this header can be checked against that declaration. The preprocessor lines form a header guard: if the header is included twice in one translation unit, the second pass does not repeat its declarations.
#pragma once is a widely supported alternative guard: put it at the top of the header instead of the #ifndef / #define / #endif pair. It is convenient but is not part of the ISO C++ language standard, so a conventional guard is the portable choice when toolchain support is uncertain. Use one guard style per header, not both merely out of habit.
include/greeting.hpp
#ifndef GREETING_HPP
#define GREETING_HPP
const char* greeting();
#endifAlternative header guard on supporting toolchains
#pragma once
const char* greeting();Define the function and call it
Save the second block as src/greeting.cpp. It includes the declaration it implements, then defines the function body. A definition supplies the work the function performs; in this case it returns a string literal. Save the third block as src/main.cpp. It includes <iostream> for std::cout and greeting.hpp for the function declaration, then calls greeting() and prints the returned message.
Both source files are translation units after preprocessing. Including greeting.hpp in main.cpp does not paste the body of greeting.cpp into it. If you build only main.cpp, compilation may succeed but linking fails because the definition is missing. In this simple project there should be one definition of greeting and one main function in the final executable.
src/greeting.cpp
#include "greeting.hpp"
const char* greeting() {
return "Hello, Codectionary!";
}src/main.cpp
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
return 0;
}Build both source files
From hello-project/, compile both .cpp files with G++ on Linux or Apple Clang on macOS. -Iinclude adds the include/ directory to the compiler’s header search path, and -o build/hello names the output executable. Create build/ first if it does not exist. Run ./build/hello only after the build succeeds. It should print Hello, Codectionary!.
The compiler driver may create temporary object files, or a build system may keep them in build/. Each object file represents compiled work for a translation unit; the linker combines them with needed standard-library code into an executable. On Windows an MSVC or MinGW command and the executable suffix differ; the CMake guide shows how to describe this layout for a configured toolchain.
Linux with G++, from hello-project/
mkdir -p build
g++ -std=c++17 -Iinclude src/main.cpp src/greeting.cpp -o build/hello
./build/hellomacOS with Apple Clang, from hello-project/
mkdir -p build
clang++ -std=c++17 -Iinclude src/main.cpp src/greeting.cpp -o build/hello
./build/helloWhere libraries and build directories fit
A library collects reusable compiled code or interfaces for another target to use; it can be your own or supplied by a toolchain or dependency. This tiny project builds one executable directly rather than a separate library. If greeting.cpp later belongs to a reusable library, the executable must link that library as well as see its public declarations. A header alone cannot supply an ordinary out-of-line definition to the linker.
An include directory tells the compiler where to search for headers such as greeting.hpp. A build directory holds generated files such as object files, build-system metadata and executables. Keeping it separate from source makes it easier to rebuild and keeps generated artifacts out of ordinary source edits. The exact output layout depends on the build system and generator; do not assume every platform produces a build/hello file.
Common multi-file mistakes
If the compiler says greeting.hpp cannot be found, check the filename, the include/ directory and the -Iinclude option or build-system include setting. If the linker cannot find greeting, check that greeting.cpp is part of the build and that its signature matches the declaration. If the linker reports more than one definition, check whether a function body was placed in a header and copied into several translation units without an appropriate inline arrangement.
Start with this two-source-file example and make one change: return a different greeting, rebuild and verify the output changes. Then read the CMake guide to see how a build description records the same source list and include path without retyping the compiler command every time.
Syntax
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
return 0;
}Examples
Call a function defined in another source file
Use this as src/main.cpp with the greeting.hpp and greeting.cpp files shown above; compile both source files.
#include <iostream>
#include "greeting.hpp"
int main() {
std::cout << greeting() << '\n';
return 0;
}- The goal is to print a greeting returned by a function stored in another translation unit.
- The header gives main.cpp the declaration needed to type-check the greeting() call. greeting.cpp supplies its actual definition.
- The build must compile and link both .cpp files. Once linked, main sends the returned text and newline to std::cout.
- If you omit greeting.cpp, adding more includes does not repair the missing linker definition; add the source file to the build.
Best practices
- Keep declarations in guarded headers and ordinary function definitions in appropriate .cpp files.
- Compile and link every source file needed by the executable.
- Separate editable source from generated build output.
Runtime & versions
Compiled native applications. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
ISO C++ resources