Readability counts
Choose explicit names, linear control flow, and small interfaces. Code is read far more often than it is written.
Make intent visible →Learn the standard, then check your own code against it. Get line-level guidance for readable, typed, testable, production-ready C++—without sending your code anywhere.
#include <algorithm>
#include <ranges>
#include <string>
#include <vector>
std::vector<std::string> active_names(const std::vector<User>& users) {
std::vector<std::string> names;
for (const auto& user : users) {
if (user.is_active()) {
names.push_back(user.name());
}
}
std::ranges::sort(names);
return names;
}
Built-in utility
Get an immediate, line-by-line review based on this guide’s core principles. Every finding explains what matters and suggests a concrete next move.
FORMATIndentation · whitespace · line length · statements
INTENTNaming · RAII · type contracts · complexity
SAFETYExceptions · secrets · shell use · timeouts
SCOPEFast heuristic review · not a parser or CI replacement
01Instant style reviewLine-level, private feedback
02Core Guidelines groundedCanonical rules, with context
03Production mindedRAII, tests, security, CI
04Actionable guidanceEvery finding explains why
The north star
Great C++ feels intentional. Readers spend their attention on the problem—not decoding clever syntax, inconsistent structure, or invisible side effects.
Choose explicit names, linear control flow, and small interfaces. Code is read far more often than it is written.
Make intent visible →Validate at the edges, type public contracts, and keep side effects easy to find. Make invalid states hard to represent.
Design clear contracts →Let formatters, linters, type checkers, and tests settle mechanical questions before review begins.
Build the safety net →Express ideas directly in code.
— C++ Core Guidelines, ISO C++ guidance
Represent ownership explicitly.
Make invalid states hard to express.
The practical guide
Use these as strong defaults. Every rule has a purpose; when the rule obscures that purpose, use judgment and document the exception.
Format & name
Formatting should be boring, predictable, and automated. Names should carry enough meaning that comments explain why, not what a variable contains.
Never mix tabs and spaces. Use hanging indents for multiline expressions, and align closing delimiters with the construct that opened them.
auto report = build_report(
source,
include_metadata,
);
Many C++ teams use 100 or 120 characters, with clang-format enforcing the choice. Pick one limit, configure it once, and let the formatter enforce it.
Use one consistent naming convention: many C++ teams choose snake_case for functions and variables, PascalCase for types, and kPascalCase or UPPER_SNAKE_CASE for constants. Avoid vague containers like data, info, and utils.
invoice_totalvariable
PaymentClientclass
kMaxRetriesconstant
double calc(std::vector<Item> items, bool f) {
double x = 0;
for (auto item : items) x += item.value;
return f ? x * 1.2 : x;
}
Money invoice_total(std::span<const LineItem> line_items,
IncludeTax include_tax) {
Money subtotal{};
for (const auto& item : line_items) {
subtotal += item.price();
}
return include_tax == IncludeTax::yes
? subtotal * kTaxRate
: subtotal;
}
Modern C++ patterns
Modern C++ uses the language’s strengths without turning every abstraction into a puzzle. Prefer clear ownership, simple control flow, and named algorithms over clever templates.
Return or throw at the boundary so the happy path stays flat and readable.
Prefer if (!items.empty()) to size comparisons; use nullptr only for pointer absence.
Use == for value semantics, named predicates for domain meaning, and avoid assignment inside conditions.
Reach for range-for, standard algorithms, spans, and views before manually managing indices.
Receipt dispatch(const Order& order, Warehouse& warehouse) {
if (order.items().empty()) {
throw EmptyOrderError{order.id()};
}
if (order.status() != OrderStatus::paid) {
throw OrderNotPaidError{order.id()};
}
std::vector<Item> available;
for (const auto& item : order.items()) {
if (warehouse.has_stock(item.sku())) {
available.push_back(item);
}
}
return warehouse.dispatch(available);
}
Types & boundaries
Concrete types, spans, string views, optionals, and concepts make interfaces searchable and mistakes cheaper. They are most valuable at public APIs, domain boundaries, and code that changes often.
Accept the broadest interface you need: std::span, std::string_view, iterators, ranges, or concepts.
Return a concrete, predictable type. Use std::optional or std::expected when absence or failure is part of the contract.
Use templates and concepts when the abstraction is real; avoid type erasure until runtime polymorphism is needed.
#include <span>
#include <vector>
struct Priced {
Money price;
};
struct BasketSummary {
std::size_t item_count{};
Money total{};
};
BasketSummary summarize(std::span<const Priced> items) {
Money total{};
for (const auto& item : items) {
total += item.price;
}
return {.item_count = items.size(), .total = total};
}
Set a version floor. Declare CMAKE_CXX_STANDARD or target_compile_features, then use the modern syntax that floor supports. For current code, prefer standard vocabulary types such as std::vector<std::string> and absence types such as std::optional<std::string>.
Functions & data
std::optional, std::expected, or exceptions deliberately.Keep policy at the center. Push frameworks, I/O, and vendor details to adapters at the edge.
Errors & logging
Errors are part of your API. Catch only what you can handle, preserve context, and make the operational trail useful without leaking sensitive data.
try {
charge(card);
} catch (...) {
std::cout << "Something went wrong\n";
return false;
}
try {
auto receipt = gateway.charge(request);
} catch (const GatewayTimeout& error) {
logger.warn("Payment timed out", order.id());
throw PaymentUnavailable{order.id(), error};
}
Name the failure in the language of the caller. Keep exception hierarchies shallow.
Libraries should report errors clearly and avoid global logging configuration. Applications own sinks, levels, and formatting.
Treat tokens, credentials, payment data, and personal information as toxic. Redact at the boundary.
Project structure
acme-engine/
├── CMakeLists.txt
├── include/
│ └── acme/
│ ├── domain.hpp
│ └── service.hpp
├── src/
│ ├── domain.cpp
│ └── service.cpp
└── tests/
└── service_test.cpp
CMakeLists.txtCentralize build metadata and supported tool configuration.
src layoutIt separates public headers, implementation files, and tests so dependencies stay visible.
Prefer cohesive modules over a catch-all utils.hpp. Mirror concepts, not framework jargon.
Include order
Testing
FastUnit tests run constantly.
FocusedOne behavior, clear failure.
FaithfulIntegration tests cover real boundaries.
IndependentNo order or shared-state surprises.
#include <catch2/catch_test_macros.hpp>
TEST_CASE("discounted_total applies percentage discounts") {
CHECK(discounted_total(Money{100}, 0.10) == Money{90});
CHECK(discounted_total(Money{0}, 0.10) == Money{0});
}
Coverage is a map, not a target. Use it to find untested risk; a high percentage cannot prove useful assertions.
Reliability
eval, unsafe deserialization, and shell strings.std::jthread, task systems, or event loops with explicit cancellation.Make failure finite. Network calls need explicit timeouts, retries need backoff and a cap, queues need bounds, and destructors/shutdown paths need tests.
The modern toolchain
Use one command locally and the same command in CI. The exact tools can change; the feedback loop should remain fast and dependable.
A pragmatic baseline
Put supported configuration in CMakeLists.txt plus checked-in clang-format and clang-tidy files. Start focused; add stricter rules because they catch problems your team actually has—not because a tool offers them.
cmake_minimum_required(VERSION 3.24)
project(your_project VERSION 0.1.0 LANGUAGES CXX)
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)
target_compile_options(project_options INTERFACE
$<$<CXX_COMPILER_ID:Clang,GNU>:-Wall -Wextra -Wpedantic -Wconversion>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /permissive->
)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE project_options)
enable_testing()
# Add Catch2 or GoogleTest targets here.
NoteThis is a starting point, not universal law. Match the C++ standard, clang-tidy checks, sanitizer policy, and dependency model to the oldest platform you actually support.
Before you merge
A compact review for the risks automation cannot fully understand. Your progress is saved on this device.
The C++ bookshelf
Category-defining bestsellers and enduring practitioner favorites, selected for a useful path from first program to maintainable production systems.
Start herePrimer→Tour
Write better codeEffective→Templates
Build for changeLarge-scale→Patterns
A comprehensive path through C++ fundamentals, library types, classes, templates, and idioms.
A compact tour of modern C++ language and library features from the language’s creator.
Concise, specific guidance on type deduction, smart pointers, move semantics, lambdas, and concurrency.
A deep guide to templates, specialization, overload resolution, metaprogramming, and generic libraries.
Process, architecture, physical design, dependencies, and scaling practices for long-lived C++ systems.
The classic pattern vocabulary for object-oriented design, still useful when applied with modern C++ restraint.
Editorial noteSelections are independent and use current English-language editions reviewed July 2026. Labels are editorial recommendations, not live sales rankings. No affiliate links or paid placements.
Primary sources
This guide synthesizes standards and practice. When precision matters, follow the living source.
C++ is a standardized programming language maintained through ISO. This independent guide is not affiliated with or endorsed by ISO, WG21, or the Standard C++ Foundation. Last editorial review: July 19, 2026.
Copied to clipboard