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:
- Check the C++ version used by the project.
- Read the local build and style files.
- Follow project rules when they are clear.
- Do not use a newer C++ feature than the build allows.
- Keep public APIs stable unless the task says to change them.
Core Rules
- Use RAII. Put each resource inside an object that cleans it up.
- Keep data fixed by default. Start with
constorconstexpr. - Use strong types. Let the compiler catch bad values and bad calls.
- Show intent. Use clear names, types, and return values.
- Keep code simple. Prefer small functions and plain control flow.
- Use values when you can. Use pointers only when they show a real link or owner.
- Check errors. Do not hide or ignore a failed call.
- 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
Widgetwhen 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
deleteon 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
noexceptonly when the function can keep that promise. - Mark one-arg constructors
explicitunless auto conversion is wanted. - Use
enum classinstead of plainenum. - Avoid magic numbers. Use named constants.
- Do not use
0orNULLfor a null pointer. Usenullptr.
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
constwhen they do not change state. - Use
constexprwhen a value can be found at build time. - Use
static_assertfor rules that can be checked at build time. - Do not use
const_castto hide a weak design. - Use
autowhen the type is clear from the right side. - Write the full type when
autowould 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::expectedin 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, orreinterpret_castonly for their exact job. - Treat
reinterpret_castand 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
forloops. - 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_guardorstd::scoped_lock. - Do not call unknown code while holding a lock.
- Use one clear lock order to avoid deadlock.
- Do not use
volatilefor 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:
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:
[[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:
[[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.