Files
stack/packages/mosaic/framework/skills/code-review-excellence/reference/cpp.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

7.5 KiB

C++ Code Review Guide

C++ code review guide focused on memory safety, lifetime, API design, and performance. Examples assume C++17/20.

Table of Contents


Ownership and RAII

Prefer RAII and smart pointers

Use RAII to express ownership. Default to std::unique_ptr, use std::shared_ptr only for shared lifetime.

// ? Bad: manual new/delete with early returns
Foo* make_foo() {
    Foo* foo = new Foo();
    if (!foo->Init()) {
        delete foo;
        return nullptr;
    }
    return foo;
}

// ? Good: RAII with unique_ptr
std::unique_ptr<Foo> make_foo() {
    auto foo = std::make_unique<Foo>();
    if (!foo->Init()) {
        return {};
    }
    return foo;
}

Wrap C resources

// ? Good: wrap FILE* with unique_ptr
using FilePtr = std::unique_ptr<FILE, decltype(&fclose)>;

FilePtr open_file(const char* path) {
    return FilePtr(fopen(path, "rb"), &fclose);
}

Lifetime and References

Avoid dangling references and views

std::string_view and std::span do not own data. Make sure the owner outlives the view.

// ? Bad: returning string_view to a temporary
std::string_view bad_view() {
    std::string s = make_name();
    return s; // dangling
}

// ? Good: return owning string
std::string good_name() {
    return make_name();
}

// ? Good: view tied to caller-owned data
std::string_view good_view(const std::string& s) {
    return s;
}

Lambda captures

// ? Bad: capture reference that escapes
std::function<void()> make_task() {
    int value = 42;
    return [&]() { use(value); }; // dangling
}

// ? Good: capture by value
std::function<void()> make_task() {
    int value = 42;
    return [value]() { use(value); };
}

Copy and Move Semantics

Rule of 0/3/5

Prefer the Rule of 0 by using RAII types. If you own a resource, define or delete copy and move operations.

// ? Bad: raw ownership with default copy
struct Buffer {
    int* data;
    size_t size;
    explicit Buffer(size_t n) : data(new int[n]), size(n) {}
    ~Buffer() { delete[] data; }
    // copy ctor/assign are implicitly generated -> double delete
};

// ? Good: Rule of 0 with std::vector
struct Buffer {
    std::vector<int> data;
    explicit Buffer(size_t n) : data(n) {}
};

Delete unwanted copies

struct Socket {
    Socket() = default;
    ~Socket() { close(); }

    Socket(const Socket&) = delete;
    Socket& operator=(const Socket&) = delete;
    Socket(Socket&&) noexcept = default;
    Socket& operator=(Socket&&) noexcept = default;
};

Const-Correctness and API Design

Use const and explicit

class User {
public:
    const std::string& name() const { return name_; }
    void set_name(std::string name) { name_ = std::move(name); }

private:
    std::string name_;
};

struct Millis {
    explicit Millis(int v) : value(v) {}
    int value;
};

Avoid object slicing

struct Shape { virtual ~Shape() = default; };
struct Circle : Shape { void draw() const; };

// ? Bad: slices Circle into Shape
void draw(Shape shape);

// ? Good: pass by reference
void draw(const Shape& shape);

Use override and final

struct Base {
    virtual void run() = 0;
};

struct Worker final : Base {
    void run() override {}
};

Error Handling and Exception Safety

Prefer RAII for cleanup

// ? Good: RAII handles cleanup on exceptions
void process() {
    std::vector<int> data = load_data(); // safe cleanup
    do_work(data);
}

Do not throw from destructors

struct File {
    ~File() noexcept { close(); }
    void close();
};

Use expected results for normal failures

// ? Expected error: use optional or expected
std::optional<int> parse_int(const std::string& s) {
    try {
        return std::stoi(s);
    } catch (...) {
        return std::nullopt;
    }
}

Concurrency

Protect shared data

// ? Bad: data race
int counter = 0;
void inc() { counter++; }

// ? Good: atomic
std::atomic<int> counter{0};
void inc() { counter.fetch_add(1, std::memory_order_relaxed); }

Use RAII locks

std::mutex mu;
std::vector<int> data;

void add(int v) {
    std::lock_guard<std::mutex> lock(mu);
    data.push_back(v);
}

Performance and Allocation

Avoid repeated allocations

// ? Bad: repeated reallocation
std::vector<int> build(int n) {
    std::vector<int> out;
    for (int i = 0; i < n; ++i) {
        out.push_back(i);
    }
    return out;
}

// ? Good: reserve upfront
std::vector<int> build(int n) {
    std::vector<int> out;
    out.reserve(static_cast<size_t>(n));
    for (int i = 0; i < n; ++i) {
        out.push_back(i);
    }
    return out;
}

String concatenation

// ? Bad: repeated allocation
std::string join(const std::vector<std::string>& parts) {
    std::string out;
    for (const auto& p : parts) {
        out += p;
    }
    return out;
}

// ? Good: reserve total size
std::string join(const std::vector<std::string>& parts) {
    size_t total = 0;
    for (const auto& p : parts) {
        total += p.size();
    }
    std::string out;
    out.reserve(total);
    for (const auto& p : parts) {
        out += p;
    }
    return out;
}

Templates and Type Safety

Prefer constrained templates (C++20)

// ? Bad: overly generic
template <typename T>
T add(T a, T b) {
    return a + b;
}

// ? Good: constrained
template <typename T>
requires std::is_integral_v<T>
T add(T a, T b) {
    return a + b;
}

Use static_assert for invariants

template <typename T>
struct Packet {
    static_assert(std::is_trivially_copyable_v<T>,
        "Packet payload must be trivially copyable");
    T payload;
};

Tooling and Build Checks

# Warnings
clang++ -Wall -Wextra -Werror -Wconversion -Wshadow -std=c++20 ...

# Sanitizers (debug builds)
clang++ -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
clang++ -fsanitize=thread -fno-omit-frame-pointer -g ...

# Static analysis
clang-tidy src/*.cpp -- -std=c++20

# Formatting
clang-format -i src/*.cpp include/*.h

Review Checklist

Safety and Lifetime

  • Ownership is explicit (RAII, unique_ptr by default)
  • No dangling references or views
  • Rule of 0/3/5 followed for resource-owning types
  • No raw new/delete in business logic
  • Destructors are noexcept and do not throw

API and Design

  • const-correctness is applied consistently
  • Constructors are explicit where needed
  • Override/final used for virtual functions
  • No object slicing (pass by ref or pointer)

Concurrency

  • Shared data is protected (mutex or atomics)
  • Locking order is consistent
  • No blocking while holding locks

Performance

  • Unnecessary allocations avoided (reserve, move)
  • Copies avoided in hot paths
  • Algorithmic complexity is reasonable

Tooling and Tests

  • Builds clean with warnings enabled
  • Sanitizers run on critical code paths
  • Static analysis (clang-tidy) results are addressed