| name | cpp-development |
| description | Use when writing, reviewing, or refactoring C++ code — covers workflow, essential patterns, and pre-commit checklist for modern C++17/20. |
C++ Development
Workflow
- Analyze — Review build system, compiler flags (
-Wall -Wextra -Wpedantic), and C++ standard version
- Design with concepts — Define type-safe interfaces using C++20 concepts before implementation
- Implement zero-cost — Apply RAII,
constexpr, smart pointers, and zero-overhead abstractions
- Verify — Run AddressSanitizer (
-fsanitize=address) and UBSan (-fsanitize=undefined); run static analysis
- Optimize — Profile first (
perf, valgrind --tool=callgrind); measure before and after any change
Essential Patterns
Rule of Zero
Let the compiler generate all special members. Compose from types that already manage their own resources.
struct Employee {
std::string name;
std::string department;
int id{0};
};
Rule of Five
If you manage a raw resource directly, define (or =delete) all five special members.
class Buffer {
public:
explicit Buffer(std::size_t n)
: data_{std::make_unique<char[]>(n)}, size_{n} {}
~Buffer() = default;
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
Buffer(const Buffer& o)
: data_{std::make_unique<char[]>(o.size_)}, size_{o.size_} {
std::copy_n(o.data_.get(), size_, data_.get());
}
Buffer& operator=(const Buffer& o) {
if (this != &o) {
auto nd = std::make_unique<char[]>(o.size_);
std::copy_n(o.data_.get(), o.size_, nd.get());
data_ = std::move(nd);
size_ = o.size_;
}
return *this;
}
private:
std::unique_ptr<char[]> data_;
std::size_t size_;
};
Parameter Passing
void print(int x);
void analyze(const std::string& data);
void consume(std::string s);
Smart Pointers
auto widget = std::make_unique<Widget>("cfg");
auto cache = std::make_shared<Cache>(1024);
void render(const Widget* w);
RAII + const Correctness (combined example)
class Sensor {
public:
explicit Sensor(std::string id) : id_{std::move(id)} {}
const std::string& id() const { return id_; }
double reading() const { return val_; }
void record(double v) { val_ = v; }
private:
const std::string id_;
double val_{0.0};
};
void update(Sensor& s, double v) {
std::lock_guard<std::mutex> lock{mtx_};
s.record(v);
}
Concepts for Template Constraints (C++20)
#include <concepts>
template<std::integral T>
T gcd(T a, T b) {
while (b) a = std::exchange(b, a % b);
return a;
}
template<typename T>
concept Serializable = requires(const T& t) {
{ t.serialize() } -> std::convertible_to<std::string>;
};
template<Serializable T>
void save(const T& obj, std::string_view path);
enum class, Not Plain enum
enum class Color { red, green, blue };
enum class LogLevel { debug, info, warning, error };
Pre-Commit Checklist
Common Pitfalls
-
Calling virtual functions in constructors/destructors — the derived override is not active yet; behavior is undefined or wrong silently.
-
memset/memcpy on non-trivial types — bypasses constructors/destructors; use assignment or std::copy instead.
-
Unnamed lock_guard: std::lock_guard<std::mutex>(m); — this is a temporary that destructs immediately, leaving the mutex unlocked. Always name it: std::lock_guard<std::mutex> lk{m};.
-
volatile for thread synchronization — volatile prevents compiler reordering but provides no CPU memory-order guarantees. Use std::atomic<T> or proper mutexes.
-
Catching exceptions by value — catches a sliced copy. Always catch (const MyException& e), never catch (MyException e).