---
name: cpp-coding-standards
description: Write, review, or refactor safe and clear C++17, C++20, and C++23 code using the C++ Core Guidelines.
origin: ECC
---

# C++ Coding Standards

This skill comes from **ECC**. Credit belongs to the original author.

Use the C++ Core Guidelines as the main guide. Write code that is safe, clear, simple, and easy to change.

## When to Use This Skill

Use this skill when you:

- Write C++ classes, functions, or templates
- Review or fix C++ code
- Plan a C++ project
- Set a shared code style
- Choose between language tools
- Work with memory, files, locks, or other resources

## First Steps

Before you change code:

1. Check the C++ version used by the project.
2. Read the local build and style files.
3. Follow project rules when they are clear.
4. Do not use a newer C++ feature than the build allows.
5. Keep public APIs stable unless the task says to change them.

## Core Rules

1. **Use RAII.** Put each resource inside an object that cleans it up.
2. **Keep data fixed by default.** Start with `const` or `constexpr`.
3. **Use strong types.** Let the compiler catch bad values and bad calls.
4. **Show intent.** Use clear names, types, and return values.
5. **Keep code simple.** Prefer small functions and plain control flow.
6. **Use values when you can.** Use pointers only when they show a real link or owner.
7. **Check errors.** Do not hide or ignore a failed call.
8. **Avoid leaks and races.** Memory, files, locks, and threads must be safe.

## Key C++ Core Rules

| Rule | Meaning |
|------|---------|
| **P.1** | Show ideas in the code itself. |
| **P.3** | Make intent clear. |
| **P.4** | Keep the program type-safe. |
| **P.5** | Prefer checks at build time. |
| **P.8** | Do not leak resources. |
| **P.10** | Prefer fixed data over data that can change. |
| **I.1** | Make APIs clear. |
| **I.2** | Avoid global data that can change. |
| **I.4** | Use exact and strong types in APIs. |

## Ownership and Pointers

Choose a type that shows who owns an object:

- Use a value such as `Widget` when no pointer is needed.
- Use `std::unique_ptr<T>` for one owner.
- Use `std::shared_ptr<T>` only when owners truly share the same life span.
- Use `std::weak_ptr<T>` to watch shared data without owning it.
- Use `T*` for a link that does not own the object and may be null.
- Use `T&` for a link that does not own the object and must not be null.
- Use `std::span<T>` for a view of a list when the project supports it.
- Do not make a smart pointer just to pass an object to a function.
- Do not build two smart pointers from the same raw pointer.
- Do not use `delete` on memory owned by a smart pointer.
- Do not return a pointer or reference to local data.

## Resource Safety

Use RAII for all resources, not only memory. This includes:

- Files
- Locks
- Sockets
- Threads
- Handles
- Database work
- Temporary state

Do not pair manual open and close calls when a safe wrapper can do both.

## Functions and APIs

- Keep functions short and focused.
- Pass small values by value.
- Pass large read-only values by `const T&`.
- Return values instead of output pointer args when this is clear.
- Use `[[nodiscard]]` for results that callers must check.
- Use `noexcept` only when the function can keep that promise.
- Mark one-arg constructors `explicit` unless auto conversion is wanted.
- Use `enum class` instead of plain `enum`.
- Avoid magic numbers. Use named constants.
- Do not use `0` or `NULL` for a null pointer. Use `nullptr`.

## Classes

- Start with the Rule of Zero. Let member types manage their own cleanup.
- If a class owns a raw resource, define or block copy and move steps with care.
- Keep class rules true after each public call.
- Hide data that callers should not change.
- Use inheritance only for a real "is a" link.
- Give a base class a virtual destructor if it may be deleted through a base pointer.
- Prefer simple parts joined together over deep class trees.

## Const and Build-Time Checks

- Mark data and methods `const` when they do not change state.
- Use `constexpr` when a value can be found at build time.
- Use `static_assert` for rules that can be checked at build time.
- Do not use `const_cast` to hide a weak design.
- Use `auto` when the type is clear from the right side.
- Write the full type when `auto` would hide key facts.

## Errors

Follow the error style already used by the project.

- Use exceptions when the project allows them and the error cannot be handled there.
- Use a result type, such as `std::expected` in C++23, when failure is a normal result.
- Do not throw from destructors.
- Do not catch every error and then ignore it.
- Do not use exceptions across C APIs or other borders that cannot carry them.
- Check for null before use when null is allowed.

## Casts and Low-Level Code

- Avoid C-style casts.
- Use `static_cast`, `dynamic_cast`, `const_cast`, or `reinterpret_cast` only for their exact job.
- Treat `reinterpret_cast` and pointer math as high-risk code.
- Keep low-level code small and behind a clear API.
- Do not read data through the wrong type.
- Check size, range, sign, and overflow when values cross type bounds.

## Loops and Lists

- Prefer range-based `for` loops.
- Use standard library tools when they make the code clearer.
- Check list bounds.
- Use signed and unsigned values with care.
- Do not keep iterators, pointers, or references after a list change may make them invalid.

## Threads

- Share as little data as possible.
- Use RAII lock types such as `std::lock_guard` or `std::scoped_lock`.
- Do not call unknown code while holding a lock.
- Use one clear lock order to avoid deadlock.
- Do not use `volatile` for thread safety.
- Make sure each thread is joined, stopped, or owned by a safe wrapper.

## Edge Cases

Check these before you finish:

- Empty input
- Null links
- Moved-from objects
- Self-copy and self-move
- Very large sizes
- Negative values
- Signed and unsigned mixes
- Integer overflow
- Failed file or memory work
- Exceptions during setup
- Early returns while a resource is held
- Object life span across async work
- Iterator or pointer invalidation
- Shared pointer cycles
- Base class deletion
- Code built with exceptions or RTTI turned off

## Concrete Example

Bad code:

```cpp
Widget* make_widget(const char* name)
{
    Widget* item = new Widget;
    item->name = name;
    return item;
}

void use_widget()
{
    Widget* item = make_widget("clock");
    run(*item);
}
```

This code has unclear ownership and leaks memory.

Better code:

```cpp
[[nodiscard]] std::unique_ptr<Widget> make_widget(std::string name)
{
    return std::make_unique<Widget>(std::move(name));
}

void use_widget()
{
    auto item = make_widget("clock");
    run(*item);
}
```

If a pointer is not needed, prefer a value:

```cpp
[[nodiscard]] Widget make_widget(std::string name)
{
    return Widget{std::move(name)};
}

void use_widget()
{
    const Widget item = make_widget("clock");
    run(item);
}
```

## Review Checklist

Before you give the code back, confirm that:

- The code builds with the project C++ version.
- Tests pass.
- Ownership is clear.
- Resources clean themselves up.
- Null and empty cases are safe.
- No local pointer or reference escapes its life span.
- Public API changes are needed and named.
- Error results are checked.
- Casts and raw memory work are rare and explained.
- The code is as simple as the task allows.