| name | programming-cpp |
| user-invocable | false |
| description | Internal skill invoked by /programming chain. Use when writing, reviewing, or refactoring C++ code — applying Core Guidelines patterns, choosing between raw and smart pointers, setting up GoogleTest/CMake, or diagnosing sanitizer reports. Trigger keywords: C++, cpp, gtest, gmock, cmake, ctest, RAII, unique_ptr, shared_ptr, constexpr, noexcept, enum class, concepts, scoped_lock, Rule of Zero, Rule of Five. |
C++ Patterns
Use the type system and RAII to prevent errors at compile time; everything else follows from that.
Modern C++ (C++17/20/23) patterns derived from the C++ Core Guidelines, plus GoogleTest/CMake testing workflow.
First Step: Dependency Scan
Before writing or modifying any C++ code, always run clang-scan-deps to understand the module/header dependency graph of the files you're about to touch. This tells you what will be affected by your changes and prevents surprises from transitive includes.
clang-scan-deps -compilation-database compile_commands.json <target-file>
If compile_commands.json doesn't exist, generate it first (CMake: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON; Bear: bear -- make).
When to Use
- Writing new C++ classes, functions, or templates
- Reviewing C++ code for safety and idiom compliance
- Setting up or fixing GoogleTest/CTest infrastructure
- Debugging test failures, flaky tests, or sanitizer reports
- Choosing between language features (raw vs smart pointer, enum vs enum class, etc.)
When NOT to Use
- Non-C++ projects
- Legacy C codebases that cannot adopt modern features
- Bare-metal/embedded where specific guidelines conflict with hardware constraints (adapt selectively)
Cross-Cutting Principles
These six themes recur across every section. When in doubt, apply them:
- RAII everywhere — bind resource lifetime to object lifetime (P.8, R.1)
- Immutability by default — start
const/constexpr; mutability is the exception (Con.1-5, ES.25)
- Type safety — use the type system to prevent errors at compile time (P.4, Enum.3)
- Express intent — names, types, and concepts communicate purpose (P.3, T.10)
- Value semantics over pointer semantics — return by value, prefer scoped objects (F.20, R.5)
- Minimize complexity — simple code is correct code (F.2-3, Per.1)
Functions
void print(int x);
void analyze(const std::string& data);
void consume(std::string s);
struct ParseResult { std::string token; int position; };
ParseResult parse(std::string_view input);
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
Anti-patterns: returning T&&, C-style variadics, capturing by reference in cross-thread lambdas.
Classes
| Rule | Guideline |
|---|
| C.2 | class if invariant; struct if members vary independently |
| C.20 | Rule of Zero — avoid defining special members when possible |
| C.21 | Rule of Five — if you define one, handle all five |
| C.35 | Base destructor: public virtual or protected non-virtual |
| C.46 | Single-argument constructors: explicit |
| C.128 | Virtual functions: exactly one of virtual, override, final |
struct Employee {
std::string name;
std::string department;
int id;
};
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_(std::make_unique<char[]>(size)), size_(size) {}
~Buffer() = default;
Buffer(const Buffer& other);
Buffer& operator=(const Buffer& other);
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
private:
std::unique_ptr<char[]> data_;
std::size_t size_;
};
Resource Management
auto widget = std::make_unique<Widget>("config");
auto cache = std::make_shared<Cache>(1024);
void render(const Widget* w) { if (w) w->draw(); }
render(widget.get());
RAII for non-memory resources:
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&&) noexcept;
private:
std::FILE* handle_;
};
Expressions & Initialization
const int max_retries{3};
const std::vector<int> primes{2, 3, 5, 7, 11};
const auto config = [&] {
Config c;
c.timeout = std::chrono::seconds{30};
c.retries = max_retries;
return c;
}();
Anti-patterns: uninitialized variables, 0/NULL instead of nullptr, C-style casts, casting away const, magic numbers.
Error Handling
class AppError : public std::runtime_error {
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;
};
try {
fetch_data(url);
} catch (const NetworkError& e) {
log_error(e.what(), e.status_code);
} catch (const AppError& e) {
log_error(e.what());
}
Concurrency
class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(value);
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty(); });
int value = queue_.front();
queue_.pop();
return value;
}
private:
std::mutex mutex_;
std::condition_variable cv_;
std::queue<int> queue_;
};
void transfer(Account& from, Account& to, double amount) {
std::scoped_lock lock(from.mutex_, to.mutex_);
from.balance_ -= amount;
to.balance_ += amount;
}
Anti-patterns: volatile for sync (CP.8), detaching threads (CP.26), unnamed lock guards, holding locks during callbacks (CP.22).
Templates & Concepts (C++20)
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) { a = std::exchange(b, a % b); }
return a;
}
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>;
};
Standard Library Quick Hits
std::vector by default; std::array for fixed-size (SL.con.1-2)
std::string owns, std::string_view observes (SL.str.1-2)
'\n' not std::endl (SL.io.50 — endl forces a flush)
enum class not plain enum; no ALL_CAPS for enumerators (Enum.3, Enum.5)
using not typedef (T.43)
#pragma once or include guards; headers self-contained (SF.8, SF.11)
Testing with GoogleTest
RED → GREEN → REFACTOR. See references/testing.md for CMake setup, coverage, and sanitizer configuration.
TEST(CalculatorTest, AddsTwoNumbers) {
EXPECT_EQ(Add(2, 3), 5);
}
class UserStoreTest : public ::testing::Test {
protected:
void SetUp() override {
store = std::make_unique<UserStore>(":memory:");
store->Seed({{"alice"}, {"bob"}});
}
std::unique_ptr<UserStore> store;
};
TEST_F(UserStoreTest, FindsExistingUser) {
auto user = store->Find("alice");
ASSERT_TRUE(user.has_value());
EXPECT_EQ(user->name, "alice");
}
class MockNotifier : public Notifier {
public:
MOCK_METHOD(void, Send, (const std::string&), (override));
};
TEST(ServiceTest, SendsNotifications) {
MockNotifier notifier;
Service service(notifier);
EXPECT_CALL(notifier, Send("hello")).Times(1);
service.Publish();
}
Use ASSERT_* for preconditions (stops test on failure), EXPECT_* for multiple checks (continues). Never sleep for sync — use condition variables or latches.
Common Mistakes
| Mistake | Fix |
|---|
Naked new/delete | make_unique/make_shared (R.11) |
| Uninitialized variables | Always initialize at declaration (ES.20) |
0 or NULL as pointer | nullptr (ES.47) |
C-style casts (int)x | static_cast<int>(x) (ES.48) |
Plain enum leaking names | enum class (Enum.3) |
shared_ptr by default | unique_ptr first, shared_ptr only for shared ownership (R.21) |
Non-explicit single-arg ctor | Add explicit to prevent implicit conversion (C.46) |
| Unnamed lock guard | std::lock_guard<std::mutex>(m) destroys immediately — always name it (CP.44) |
volatile for thread sync | Use std::atomic or mutexes (CP.8) |
typedef | using (T.43) |
std::endl | '\n' — endl forces a flush (SL.io.50) |
| Unconstrained templates | Add concepts (T.10) |
Pre-Completion Checklist