SuperAPI AI Directory

cpp-coding-standards

Coding & development

View repository

Updated 10-16 07:23

About this skill

C++ Coding Standards (C++ Core Guidelines)

Derived from C++ Core Guidelines comprehensive coding standards for modern C++ (C++17/20/23). Enforce type safety, resource safety, immutability, and clarity.

When to use

  • Writing new C++ code (classes, functions, templates)
  • Reviewing or refactoring existing C++ code
  • Making architectural decisions in C++ projects
  • Enforcing consistent style in a C++ codebase
  • Choosing between language features (e.g., enum vs enum class, raw pointers vs smart pointers)

When not to use

  • Non-C++ projects
  • Legacy C codebases that cannot adopt modern C++ features
  • Embedded/bare-metal contexts where specific guidelines conflict with hardware constraints (requires selective adjustment)

Core Principles (Cross-Cutting Principles)

The following themes run throughout the guidelines and form the foundation:

  1. RAII everywhere (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime. Resource Acquisition Is Initialization (RAII)
  2. Immutability by default (P.10, Con.1-5, ES.25): Prefer const/constexpr; mutability should be the exception. Immutability
  3. Type safety (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time. Type safety
  4. Express intent (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should convey purpose. Express intent
  5. Minimize complexity (F.2-3, ES.5, Per.4-5): Simple code is correct code
  6. Value semantics over pointer semantics (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects. Value semantics

Philosophy and interfaces (P., I.)

Core rules

Rule Summary
P.1 Express ideas directly in code
P.3 Express intent
P.4 Ideally, a program should be statically type-safe
P.5 Prefer compile-time checking over run-time checking
P.8 Don't leak any resources
P.10 Prefer immutable data over mutable data
I.1 Make interfaces explicit
I.2 Avoid non-const global variables
I.4 Make interfaces precisely and strongly typed
I.11 Never transfer ownership by a raw pointer or reference
I.23 Keep the number of function arguments low

Correct examples (DO)

// P.10 + I.4: 不可变的强类型接口
struct Temperature {
    double kelvin;
};

Temperature boil(const Temperature& water);

Incorrect examples (DON'T)

// 弱接口:所有权不明确,单位不明确
double boil(double* temp);

// 非 const 全局变量
int g_counter = 0;  // 违反 I.2

Functions (F.*)

Core rules

Rule Summary
F.1 Package meaningful operations as carefully named functions
F.2 A function should perform a single logical operation
F.3 Keep functions short and simple
F.4 If a function may be evaluated at compile time, declare it constexpr
F.6 If a function never throws an exception, declare it noexcept
F.8 Prefer pure functions
F.16 For "input" parameters, pass low-cost copy types by value, and other types by const& Pass
F.20 For "output" values, prefer return values over output parameters
F.21 To return multiple "output" values, prefer returning a struct
F.43 Never return a pointer or reference to a local object

Parameter passing

// F.16: 低开销类型按值传递,其他按 const& 传递
void print(int x);                           // 低开销:按值
void analyze(const std::string& data);       // 高开销:按 const&
void transform(std::string s);               // 接收端:按值(将触发 move)

// F.20 + F.21: 使用返回值,而非输出参数
struct ParseResult {
    std::string token;
    int position;
};

ParseResult parse(std::string_view input);   // 推荐:返回结构体

// 不良实践:使用输出参数
void parse(std::string_view input,
           std::string& token, int& pos);    // 避免这样做

Pure functions and constexpr

// F.4 + F.8: 尽可能使用纯函数和 constexpr
constexpr int factorial(int n) noexcept {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

static_assert(factorial(5) == 120);

Anti-Patterns

  • Returning T&& (F.45)
  • Using va_arg / C-style variadic arguments (F.55)
  • Capturing by reference in a lambda passed to another thread (F.53)
  • Returning const T, which inhibits move semantics (F.49)

Classes and class hierarchies (C.*)

Core rules

Rule Summary
C.2 If an invariant exists, use class; if member data varies independently, use struct
C.9 Minimize the public exposure of members
C.20 If you can avoid defining default operations, do not define them (Rule of Zero)
C.21 If you define or =delete any copy/move/destructor function, handle all five (Rule of Five)
C.35 Base class destructor: either public virtual or protected non-virtual
C.41 A constructor should create a fully initialized object
C.46 Declare single-argument constructors as explicit
C.67 A polymorphic class should suppress public copy/move
C.128 Virtual functions: must specify exactly one of virtual、override or final one of

Rule of Zero

// C.20: 让编译器生成特殊成员
struct Employee {
    std::string name;
    std::string department;
    int id;
    // 无需析构函数、拷贝/移动构造函数或赋值运算符
};

Rule of Five

// C.21: 如果必须管理资源,请定义所有五个
class Buffer {
public:
    explicit Buffer(std::size_t size)
        : data_(std::make_unique<char[]>(size)), size_(size) {}

    ~Buffer() = default;

    Buffer(const Buffer& other)
        : data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
        std::copy_n(other.data_.get(), size_, data_.get());
    }

    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            auto new_data = std::make_unique<char[]>(other.size_);
            std::copy_n(other.data_.get(), other.size_, new_data.get());
            data_ = std::move(new_data);
            size_ = other.size_;
        }
        return *this;
    }

    Buffer(Buffer&&) noexcept = default;
    Buffer& operator=(Buffer&&) noexcept = default;

private:
    std::unique_ptr<char[]> data_;
    std::size_t size_;
};

Class hierarchies

// C.35 + C.128: 虚析构函数,使用 override
class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;  // C.121: 纯接口
};

class Circle : public Shape {
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return 3.14159 * radius_ * radius_; }

private:
    double radius_;
};

Anti-Patterns

  • Calling virtual functions in constructors/destructors (C.82)
  • Using memset/memcpy (C.90)
  • Providing different default arguments for virtual functions and their overriders (C.140)
  • Making data members const or references, which inhibits move/copy (C.12)

Resource management (R.*)

Core rules

Rule Summary
R.1 Use RAII to manage resources automatically
R.3 Raw pointers (T*) are non-owning
R.5 Prefer scoped objects; do not allocate on the heap unnecessarily
R.10 Avoid using malloc()/free()
R.11 Avoid explicitly calling new and delete
R.20 Use unique_ptr or shared_ptr to express ownership
R.21 Unless sharing ownership, prefer unique_ptr over shared_ptr
R.22 Use make_shared() to create shared_ptr

Smart pointer usage

// R.11 + R.20 + R.21: 使用智能指针进行 RAII
auto widget = std::make_unique<Widget>("config");  // 独占所有权
auto cache  = std::make_shared<Cache>(1024);        // 共享所有权

// R.3: 原始指针 = 非所有权的观察者
void render(const Widget* w) {  // 不拥有 w
    if (w) w->draw();
}

render(widget.get());

RAII pattern

// R.1: 资源获取即初始化 (Resource acquisition is initialization)
class FileHandle {
public:
    explicit FileHandle(const std::string& path)
        : handle_(std::fopen(path.c_str(), "r")) {
        if (!handle_) throw std::runtime_error("Failed to open: " + path);
    }

    ~FileHandle() {
        if (handle_) std::fclose(handle_);
    }

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    FileHandle(FileHandle&& other) noexcept
        : handle_(std::exchange(other.handle_, nullptr)) {}
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (handle_) std::fclose(handle_);
            handle_ = std::exchange(other.handle_, nullptr);
        }
        return *this;
    }

private:
    std::FILE* handle_;
};

Anti-Patterns

  • Bare new/delete (R.11)
  • Used in C++ code malloc()/free() (R.10)
  • Multiple resource allocations in a single expression (R.13 — exception safety risk)
  • In unique_ptr use shared_ptr (R.21)

Expressions and statements (ES.*)

Core rules

Rule Summary
ES.5 Keep scopes small
ES.20 Always initialize objects
ES.23 Prefer {} initialization syntax
ES.25 Declare objects as const or constexpr
ES.28 For const use a lambda for complex initialization of variables
ES.45 Avoid magic constants; use symbolic constants
ES.46 Avoid narrowing/lossy arithmetic conversions
ES.47 Use nullptr instead of 0 or NULL
ES.48 Avoid casts
ES.50 Do not cast away const property

Initialization

// ES.20 + ES.23 + ES.25: 始终初始化,优先使用 {},默认设为 const
const int max_retries{3};
const std::string name{"widget"};
const std::vector<int> primes{2, 3, 5, 7, 11};

// ES.28: 用于复杂 const 初始化的 lambda
const auto config = [&] {
    Config c;
    c.timeout = std::chrono::seconds{30};
    c.retries = max_retries;
    c.verbose = debug_mode;
    return c;
}();

Anti-Patterns

  • Uninitialized variables (ES.20)
  • Use 0 or NULL as pointers (ES.47 — should use nullptr)
  • C-style casts (ES.48 — should use static_cast、const_cast etc.)
  • Cast away const property (ES.50)
  • Magic numbers without named constants (ES.45)
  • Mixed signed and unsigned arithmetic operations (ES.100)
  • Reusing names in nested scopes (ES.12)

Error handling (E.*)

Core rules

Rules Summary
E.1 Develop an error-handling strategy early in design
E.2 Throw an exception when a function cannot perform its assigned task
E.6 Use RAII to prevent leaks
E.12 Use when throwing exceptions is impossible or unacceptable noexcept
E.14 Use specially designed user-defined types as exceptions
E.15 Throw by value, catch by reference
E.16 Destructors, deallocation operations, and swap must never fail
E.17 Do not try to catch every exception in every function

Exception hierarchy

// E.14 + E.15: 自定义异常类型,按值抛出,按引用捕获
class AppError : public std::runtime_error {
public:
    using std::runtime_error::runtime_error;
};

class NetworkError : public AppError {
public:
    NetworkError(const std::string& msg, int code)
        : AppError(msg), status_code(code) {}
    int status_code;
};

void fetch_data(const std::string& url) {
    // E.2: 抛出异常以表示失败
    throw NetworkError("connection refused", 503);
}

void run() {
    try {
        fetch_data("https://api.example.com");
    } catch (const NetworkError& e) {
        log_error(e.what(), e.status_code);
    } catch (const AppError& e) {
        log_error(e.what());
    }
    // E.17: 不要在此处捕获所有异常 —— 让非预期错误继续传播
}

Anti-Patterns

  • Throwing built-in types such as int or string literals (E.14)
  • Catching by value (risk of object slicing) (E.15)
  • Empty catch blocks that silently swallow errors
  • Using exceptions for control flow (E.3)
  • Error handling based on global state (such as errno) (E.28)

Constants and immutability (Con.*)

All rules

Rules Summary
Con.1 By default, make objects immutable
Con.2 By default, make member functions const
Con.3 By default, pass pointers and references to const pointers and references
Con.4 Use for values that do not change after construction const
Con.5 Use for values computable at compile time constexpr
// Con.1 到 Con.5: 默认不变性
class Sensor {
public:
    explicit Sensor(std::string id) : id_(std::move(id)) {}

    // Con.2: 默认 const 成员函数
    const std::string& id() const { return id_; }
    double last_reading() const { return reading_; }

    // 仅在需要修改时才使用非 const
    void record(double value) { reading_ = value; }

private:
    const std::string id_;  // Con.4: 构造后绝不改变
    double reading_{0.0};
};

// Con.3: 按 const 引用传递
void display(const Sensor& s) {
    std::cout << s.id() << ": " << s.last_reading() << '\n';
}

// Con.5: 编译时常量
constexpr double PI = 3.14159265358979;
constexpr int MAX_SENSORS = 256;

Concurrency and parallelism (CP.*)

Core rules

Rules Summary
CP.2 Avoid data races
CP.3 Minimize explicit sharing of writable data
CP.4 Think in terms of tasks rather than threads
CP.8 Do not use volatile for synchronization
CP.20 Use RAII, never call directly lock()/unlock()
CP.21 Use std::scoped_lock Acquire multiple mutexes
CP.22 Never call unknown code while holding a lock
CP.42 Don't wait unconditionally
CP.44 Remember to name your lock_guard and unique_lock Naming
CP.100 Don't use lock-free programming unless absolutely necessary

Safe locking

// CP.20 + CP.44: RAII 锁,始终命名
class ThreadSafeQueue {
public:
    void push(int value) {
        std::lock_guard<std::mutex> lock(mutex_);  // CP.44: 已命名!
        queue_.push(value);
        cv_.notify_one();
    }

    int pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        // CP.42: 始终带条件等待
        cv_.wait(lock, [this] { return !queue_.empty(); });
        const int value = queue_.front();
        queue_.pop();
        return value;
    }

private:
    std::mutex mutex_;             // CP.50: 互斥锁及其保护的数据
    std::condition_variable cv_;
    std::queue<int> queue_;
};

Multiple mutexes

// CP.21: std::scoped_lock 用于多个互斥锁(避免死锁)
void transfer(Account& from, Account& to, double amount) {
    std::scoped_lock lock(from.mutex_, to.mutex_);
    from.balance_ -= amount;
    to.balance_ += amount;
}

Anti-Patterns

  • Use volatile for synchronization (CP.8 — it is only for hardware I/O)
  • Detaching threads (CP.26 — lifetime management becomes nearly impossible)
  • Unnamed lock guards:std::lock_guard<std::mutex>(m); is destroyed immediately (CP.44)
  • Holding a lock while calling a callback (CP.22 — risk of deadlock)
  • Lock-free programming without deep expertise (CP.100)

Templates and generic programming (T.*)

Core rules

Rule Summary
T.1 Use templates to raise the level of abstraction
T.2 Use templates to express algorithms for multiple argument types
T.10 Specify concepts for all template parameters
T.11 Use standard concepts whenever possible
T.13 Prefer the shorthand notation for simple concepts
T.43 Prefer using over typedef
T.120 Use template metaprogramming only when truly necessary
T.144 Do not specialize function templates (use overloading instead)

Concepts (C++20)

#include <concepts>

// T.10 + T.11: 使用标准概念约束模板
template<std::integral T>
T gcd(T a, T b) {
    while (b != 0) {
        a = std::exchange(b, a % b);
    }
    return a;
}

// T.13: 简写概念语法
void sort(std::ranges::random_access_range auto& range) {
    std::ranges::sort(range);
}

// 针对领域特定约束的自定义概念
template<typename T>
concept Serializable = requires(const T& t) {
    { t.serialize() } -> std::convertible_to<std::string>;
};

template<Serializable T>
void save(const T& obj, const std::string& path);

Anti-Patterns

  • Unconstrained templates in visible namespaces (T.47)
  • Specializing function templates instead of overloading (T.144)
  • Using template metaprogramming when constexpr is sufficient (T.120)
  • Use typedef instead of using (T.43)

Standard library (SL.*)

Core rules

Rule Summary
SL.1 Use libraries whenever possible
SL.2 Prefer the standard library over other libraries
SL.con.1 Prefer std::array or std::vector over C arrays
SL.con.2 Prefer std::vector
SL.str.1 Use std::string to own character sequences
SL.str.2 Use std::string_view to refer to character sequences
SL.io.50 Avoid endl(using '\n' —— endl forces buffer flushing)
// SL.con.1 + SL.con.2: 优先选择 vector/array 而非 C 数组
const std::array<int, 4> fixed_data{1, 2, 3, 4};
std::vector<std::string> dynamic_data;

// SL.str.1 + SL.str.2: string 拥有所有权,string_view 负责观察
std::string build_greeting(std::string_view name) {
    return "Hello, " + std::string(name) + "!";
}

// SL.io.50: 使用 '\n' 而非 endl
std::cout << "result: " << value << '\n';

Enumerations (Enum.*)

Core rules

Rule Summary
Enum.1 Prefer enumerations over macros
Enum.3 Prefer enum class over plain enum
Enum.5 Do not use ALL_CAPS naming for enumerators
Enum.6 Avoid anonymous enumerations
// Enum.3 + Enum.5: 有作用域枚举,非全大写
enum class Color { red, green, blue };
enum class LogLevel { debug, info, warning, error };

// 不良实践:普通枚举会泄露名称,全大写会与宏冲突
enum { RED, GREEN, BLUE };           // 违反 Enum.3 + Enum.5 + Enum.6
#define MAX_SIZE 100                  // 违反 Enum.1 —— 应使用 constexpr

Source files and naming (SF., NL.)

Core rules

Rule Summary
SF.1 Use .cppfor code files and .h
SF.7 Do not write using namespace
SF.8 Use .h for all #include guards
SF.11 Header files should be self-contained
NL.5 Avoid encoding type information in names (do not use Hungarian notation)
NL.8 Use a consistent naming style
NL.9 Use ALL_CAPS only for macro names
NL.10 Prefer 下划线风格 (underscore_style) naming

Header Guard

// SF.8: 包含保护 (或 #pragma once)
#ifndef PROJECT_MODULE_WIDGET_H
#define PROJECT_MODULE_WIDGET_H

// SF.11: 自包含 —— 包含此头文件所需的所有内容
#include <string>
#include <vector>

namespace project::module {

class Widget {
public:
    explicit Widget(std::string name);
    const std::string& name() const;

private:
    std::string name_;
};

}  // namespace project::module

#endif  // PROJECT_MODULE_WIDGET_H

Naming Conventions

// NL.8 + NL.10: 一致的下划线风格
namespace my_project {

constexpr int max_buffer_size = 4096;  // NL.9: 非全大写(它不是宏)

class tcp_connection {                 // 下划线风格的类名
public:
    void send_message(std::string_view msg);
    bool is_connected() const;

private:
    std::string host_;                 // 成员变量带后缀下划线
    int port_;
};

}  // namespace my_project

Anti-Patterns

  • Use in the global scope of a header file using namespace std; (SF.7)
  • Header files that depend on include order (SF.10, SF.11)
  • Hungarian notation, such as strName、iCount (NL.5)
  • ALL_CAPS for anything other than macros (NL.9)

Performance (Per.*)

Core Rules

Rule Summary
Per.1 Do not optimize without reason
Per.2 Do not optimize prematurely
Per.6 Without measurement, do not draw conclusions about performance
Per.7 Design that aims to support optimization
Per.10 Rely on the static type system
Per.11 Move computation from runtime to compile time
Per.19 Access memory in a predictable way

Guidelines

// Per.11: 尽可能在编译时计算
constexpr auto lookup_table = [] {
    std::array<int, 256> table{};
    for (int i = 0; i < 256; ++i) {
        table[i] = i * i;
    }
    return table;
}();

// Per.19: 优先选择连续数据以实现缓存友好
std::vector<Point> points;           // 推荐:连续存储
std::vector<std::unique_ptr<Point>> indirect_points; // 不良实践:指针追踪

Anti-Patterns

  • Optimizing without performance profiling data (Per.1, Per.6)
  • Choosing "clever" low-level code over clear abstractions (Per.4, Per.5)
  • Ignoring data layout and cache behavior (Per.19)

Quick Reference Self-Check List

Before marking C++ work as complete:

  • No raw new/delete — use smart pointers or RAII (R.11)
  • Objects are initialized at declaration (ES.20)
  • Variables are set to by default const/constexpr (Con.1, ES.25)
  • Member functions are set to as much as possible const (Con.2)
  • Use enum class instead of plain enum (Enum.3)
  • Use nullptr instead of 0/NULL (ES.47)
  • No narrowing conversions (ES.46)
  • No C-style casts (ES.48)
  • Single-argument constructors are set to explicit (C.46)
  • The Rule of Zero or Rule of Five is applied (C.20, C.21)
  • Base class destructors are public virtual or protected non-virtual (C.35)
  • Templates are constrained by Concepts (T.10)
  • In the global scope of a header file, there is no using namespace (SF.7)
  • Header files have include guards and are self-contained (SF.8, SF.11)
  • Locks use RAII (scoped_lock/lock_guard) (CP.20)
  • Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)
  • Use '\n' instead of std::endl (SL.io.50)
  • No magic numbers (ES.45)