Split code across headers and source files, use include guards and forward declarations to speed builds, and organize names with namespaces and scope.
Declarations go in a header (.hpp) that other files #include; definitions go in a matching source file (.cpp). #pragma once (or classic include guards) stops a header being pasted in twice. This separation is what lets a project compile in parallel and link together.
// ---------- shape.hpp ----------
#pragma once // include exactly once per translation unit
class Shape {
public:
double area() const; // declaration only
};
// ---------- shape.cpp ----------
#include "shape.hpp"
double Shape::area() const { // definition lives here
return 0.0;
}
// ---------- main.cpp ----------
#include "shape.hpp" // sees the declaration; links to the definition
int main() { Shape s; return static_cast<int>(s.area()); }When a header only uses a pointer or reference to a type, you can forward-declare it (class Foo;) instead of including its header. This cuts compile-time dependencies and breaks include cycles. Include the full header only in the .cpp where you actually use the type’s members.
// ---------- editor.hpp ----------
#pragma once
class Document; // forward declaration — no #include needed
class Editor {
Document* doc_; // a pointer only needs the name
public:
void open(Document* d);
};
// ---------- editor.cpp ----------
#include "editor.hpp"
#include "document.hpp" // full definition needed HERE to use members
void Editor::open(Document* d) { doc_ = d; }A namespace groups related names and prevents collisions between libraries. Qualify names with :: (std::vector), or bring specific names into scope with using. Avoid using namespace std; in headers — it pollutes every file that includes them.
#include <iostream>
namespace geometry {
struct Point { double x, y; };
double norm(const Point& p) { return p.x * p.x + p.y * p.y; }
}
int main() {
geometry::Point p{3, 4};
using geometry::norm; // bring just this name in
std::cout << norm(p) << "\n"; // 25
return 0;
}