effective-cpp

Use this skill when writing, reviewing, refactoring, or debugging C++ code — especially when the task involves class design, resource management (RAII, smart pointers, new/delete), inheritance hierarchies (virtual destructors, NVI, slicing), move semantics (std::move, std::forward, noexcept), templates (SFINAE, CRTP, type traits), exception safety, concurrency (std::atomic vs volatile, std::thread, std::async), or any C++ best practices question. Provides prioritized rules and anti-pattern detection from Scott Meyers' Effective C++, More Effective C++, and Effective Modern C++. Trigger this skill whenever user mentions C++ classes, constructors/destructors, copy/move operations, Rule of Five/Zero, const correctness, smart pointer ownership, polymorphism, or asks for C++ code review. Do NOT trigger for pure C (C89/C99), build tools (CMake/Make), test frameworks (GTest), or other languages.

install
source · Clone the upstream repo
git clone https://github.com/catiicode/claude-code-awesome-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/catiicode/claude-code-awesome-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/effective-cpp" ~/.claude/skills/catiicode-claude-code-awesome-skills-effective-cpp && rm -rf "$T"
manifest: effective-cpp/SKILL.md
source content

Effective C++ Development Guide

Best practices from Scott Meyers' three books, organized by priority for practical C++ development.

Sources: [EC] Effective C++ (55 Items) | [MEC] More Effective C++ (35 Items) | [EMC] Effective Modern C++ (42 Items)

Priority: IRON RULE (UB/crash/leak) > STRONG (correctness/maintainability) > GOOD (quality improvement)

C++ Baseline: C++11

Deep-dive references — read when working on a specific topic:


Iron Rules — Violating These Causes UB, Crashes, or Leaks

#RuleKey Point
1RAII [EC-13, MEC-9, MEC-10]Every resource owned by an object whose destructor releases it. No manual cleanup. Smart pointer members in constructors prevent partial-construction leaks.
2Match new/delete forms [EC-16]
new
delete
,
new[]
delete[]
. Mismatch = UB. Prefer containers over raw arrays.
3Virtual destructor for polymorphic bases [EC-7]Deleting derived via base pointer with non-virtual dtor = UB. Has virtual functions → needs virtual dtor.
4No exceptions from destructors [EC-8, MEC-11]Second exception during stack unwinding →
std::terminate()
. Wrap throws in try/catch. Provide separate close() for error handling.
5No virtual calls in ctors/dtors [EC-9]During base construction, dynamic type IS the base. Virtual calls resolve to base version, never derived. Pass info via ctor args.
6No redefining non-virtual functions or default params [EC-36, EC-37]Both are statically bound. Redefining creates confusing behavior depending on pointer type. Use NVI for defaults.
7No polymorphic arrays [MEC-3]
sizeof(Base)
sizeof(Derived)
breaks pointer arithmetic. Use
vector<unique_ptr<Base>>
.
8Smart pointer ownership [EMC-18–21]
unique_ptr
(default) /
shared_ptr
(truly shared) /
weak_ptr
(observer). Never two
shared_ptr
from same raw pointer. Use
make_unique
/
make_shared
. Store new-ed objects in smart pointers immediately [EC-17].
9Correct copying [EC-11, EC-12, EMC-17]Self-assignment safety (copy-and-swap). Copy ALL parts (members + base). Rule of Five: declare any special member → declare all five.
10std::move/forward correctness [EMC-23, EMC-25]
move
= unconditional rvalue cast.
forward
= conditional (universal refs). Apply on LAST use only. Never
move
const (silently copies). Never
move
return values (inhibits RVO).
11atomic ≠ volatile [EMC-40]
std::atomic
= thread safety.
volatile
= no optimization (hardware I/O).
volatile
provides ZERO thread safety in C++.
12Exception safety basics [EC-29, MEC-13]Catch by reference (never by value — slicing). Catch derived before base (first-fit matching). Every function: at least basic guarantee.

For full code examples (correct/incorrect pairs): See references/iron-rules.md


Anti-Pattern Quick Reference

When reviewing C++ code, flag these patterns immediately:

Code SmellRiskFix
Raw
new
/
delete
outside RAII
Leak on exception/early return
make_unique
/
make_shared
→ [Iron #1]
Non-virtual dtor + virtual functionsUB on polymorphic delete
virtual ~Base() = default;
→ [Iron #3]
catch(std::exception e)
(by value)
Slicing
catch(const std::exception& e)
→ [Iron #12]
return std::move(local)
Prevents RVO
return local;
→ [Iron #10]
[=]
or
[&]
default capture
Dangling
this
/refs
Explicit captures → [references/move-semantics-lambdas.md]
Missing Rule of FiveDouble-free / use-after-moveDeclare all 5 or use Rule of Zero → [Iron #9]
volatile
for threading
Data race (UB)
std::atomic
→ [Iron #11]
Virtual call in ctor/dtorCalls base version silentlyPass data via ctor args → [Iron #5]
Redefining non-virtual base funcStatic binding confusionMake it virtual or use different name → [Iron #6]
public/protected data membersNo encapsulationPrivate + accessors → [references/class-design.md]
Two
shared_ptr
from same raw ptr
Double control block / double-free
make_shared
or copy existing
shared_ptr
→ [Iron #8]
Joinable
std::thread
at scope exit
std::terminate()
RAII thread guard → [references/concurrency.md]
#define
constants/macros
No scope, no type safety
constexpr
+
inline
functions → [references/class-design.md]
auto x = {val}
in C++11
Deduces
initializer_list
, not intended type
Use
auto x = val;
or explicit type → [references/exceptions-interop.md]
C-style casts
(int*)ptr
Hides intent, error-prone
static_cast
/
const_cast
/etc. → [references/exceptions-interop.md]

Scenario Checklists

Creating a New Class

→ Deep dive: references/class-design.md

  1. Rule of Zero (no resource mgmt) or Rule of Five (manages resources)
  2. Polymorphic base → virtual dtor. Non-base → no virtual dtor
  3. All data members
    private
  4. explicit
    on single-argument constructors
  5. Member init lists in declaration order
  6. If copyable: self-assignment safety (copy-and-swap)
  7. If movable: move operations marked
    noexcept
  8. mutable
    members → protect with
    std::mutex
    or
    std::atomic
  9. Mark all overrides with
    override
  10. = delete
    or
    = default
    each special member explicitly

Managing Resources

→ Deep dive: references/resource-management.md

  1. Every resource has an RAII owner
  2. No raw
    new
    /
    delete
    outside RAII wrappers
  3. make_unique
    /
    make_shared
    over direct
    new
  4. unique_ptr
    (default) vs
    shared_ptr
    (only when truly shared)
  5. weak_ptr
    to break cycles or non-owning observation
  6. new[]
    delete[]
    form match
  7. New-ed objects into smart pointers in standalone statements
  8. Smart pointer members in ctors → safe partial-construction cleanup

Designing Inheritance

→ Deep dive: references/inheritance-templates.md

  1. Public inheritance = "is-a" — every base operation valid for derived
  2. Pure virtual = interface. Virtual = interface + default. Non-virtual = invariant
  3. Never redefine non-virtual functions or inherited default params
  4. override
    on all overrides.
    using Base::func;
    for hidden overloads
  5. Prefer composition over inheritance for "has-a" / "implemented-in-terms-of"
  6. Consider NVI (public non-virtual → private virtual)
  7. Make non-leaf classes abstract to prevent slicing
  8. Never pass arrays polymorphically —
    vector<unique_ptr<Base>>

Using Move Semantics

→ Deep dive: references/move-semantics-lambdas.md

  1. std::move
    on rvalue refs,
    std::forward
    on universal refs
  2. Apply on last use of parameter only
  3. Never
    std::move
    a return value — inhibits RVO
  4. std::move
    on const → silently copies
  5. Mark move operations
    noexcept
  6. Don't overload on universal references — use
    enable_if
    or tag dispatch
  7. Know forwarding failures: braced inits, 0/NULL, bitfields, overloaded names
  8. In generic code: assume moves may not exist or be cheap

Writing Templates

→ Deep dive: references/inheritance-templates.md

  1. typename
    before nested dependent type names
  2. Access templatized base class names via
    this->
    ,
    using
    , or explicit qualification
  3. Factor parameter-independent code out of templates (prevent bloat)
  4. Member function templates for generalized copy/assignment
  5. Friend functions inside templates for implicit conversions
  6. Traits + tag dispatch for compile-time algorithm selection

Concurrent Code

→ Deep dive: references/concurrency.md

  1. std::atomic
    for thread safety.
    volatile
    for hardware only
  2. Prefer
    std::async
    (task-based) over
    std::thread
  3. std::launch::async
    if asynchronicity is essential
  4. RAII wrapper for
    std::thread
    (must be unjoinable before destruction)
  5. Protect
    mutable
    members in
    const
    functions with mutex/atomic
  6. Last
    std::future
    from
    async
    blocks on destruction
  7. promise<void>
    /
    future<void>
    for one-shot event signaling

Performance Review

→ Deep dive: references/performance.md

  1. Profile first (80-20 rule). Never optimize blindly
  2. Postpone variable definitions — define at point of use
  3. Minimize casting — avoid
    dynamic_cast
    in hot paths
  4. Avoid returning handles (refs/ptrs) to object internals
  5. Limit inlining to small, frequently-called functions
  6. Reduce compilation dependencies (Pimpl, interface classes)
  7. Facilitate RVO: return by name, never
    std::move
  8. emplace
    over
    push_back
    when constructing in-place
  9. Mark move/swap
    noexcept
    . Prefer
    +=
    over
    +
    in hot paths

Exception Safety

→ Deep dive: references/exceptions-interop.md

  1. At least basic guarantee (no leaks, valid state)
  2. Destructors never throw
  3. Catch by reference, derived before base
  4. RAII for all resources — stack unwinding handles cleanup
  5. Copy-and-swap for strong guarantee
  6. Exceptions for exceptional conditions only (throwing is slow)
  7. noexcept
    only when genuinely guaranteed

Strong Recommendations — Quick Summary

These significantly improve code quality. Follow unless there's a documented reason not to.

TopicKey Rules
const/constexpr [EC-2,3, EMC-15]
constexpr
for compile-time constants.
const
on parameters, return types, member functions. No
#define
.
Initialization [EC-4, EMC-7,8]Member init lists in declaration order.
nullptr
not 0/NULL. Know
{}
vs
()
differences.
Interfaces [EC-18,20,23,24]Easy to use correctly, hard to misuse. Pass-by-ref-to-const. Non-member non-friend for encapsulation. Non-member for symmetric type conversions.
Scoped enums [EMC-10]
enum class
— no namespace pollution, no implicit conversions.
= delete [EMC-11]Compile-time errors, works on any function. Declare
public
.
override [EMC-12]Catches signature mismatches. Always use it on every override.
noexcept [EMC-14]Critical for move ops and swap. Enables
vector
optimizations.
auto [EMC-5,6]Prefer auto. Beware proxy types — use
static_cast
idiom.
Non-throwing swap [EC-25]Member swap + non-member in same namespace for ADL.
Pimpl [EC-31, EMC-22]Reduce compilation dependencies. Define special members in .cpp.

For detailed guidance and code examples on each topic: See the relevant references/ file linked above.


Good to Know — Quick Summary

TopicKey Rules
Type deduction [EMC-1–4]Template deduction 3 cases.
auto
+ braced init →
initializer_list
.
decltype(auto)
for return types.
Modern syntax [EMC-9,13]
using
over
typedef
.
cbegin()
/
cend()
for read-only iteration.
Operators [EC-10,24, MEC-6,22]Assignment returns
*this&
.
+=
then
+
in terms of
+=
. Prefix
++
preferred. Non-member for conversions.
Lazy/eager eval [MEC-17,18]Lazy: COW, deferred fetch. Eager: caching, prefetching.
Custom new/delete [EC-49–52]Only when measured need. Follow conventions. Match placement forms.
TMP/traits [EC-47,48]Traits + tag dispatch for compile-time selection. TMP for zero-cost abstractions.
C interop [MEC-34]
extern "C"
, no mixed new/free, POD structs, catch at boundary.
Temporaries [MEC-19–21]Arise from implicit conversions and return-by-value. Overload common types.
explicit
constructors.