@polderlabs/bizar 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/audit.mjs +144 -0
- package/cli/banner.mjs +41 -0
- package/cli/bin.mjs +186 -0
- package/cli/copy.mjs +508 -0
- package/cli/export.mjs +87 -0
- package/cli/init.mjs +147 -0
- package/cli/install.mjs +390 -0
- package/cli/plan-templates.mjs +523 -0
- package/cli/plan.mjs +2087 -0
- package/cli/prompts.mjs +163 -0
- package/cli/update.mjs +273 -0
- package/cli/utils.mjs +153 -0
- package/config/AGENTS.md +282 -0
- package/config/agents/baldr.md +148 -0
- package/config/agents/forseti.md +112 -0
- package/config/agents/frigg.md +101 -0
- package/config/agents/heimdall.md +157 -0
- package/config/agents/hermod.md +144 -0
- package/config/agents/mimir.md +115 -0
- package/config/agents/odin.md +309 -0
- package/config/agents/quick.md +78 -0
- package/config/agents/semble-search.md +44 -0
- package/config/agents/thor.md +97 -0
- package/config/agents/tyr.md +96 -0
- package/config/agents/vidarr.md +100 -0
- package/config/agents/vor.md +140 -0
- package/config/commands/audit.md +1 -0
- package/config/commands/explain.md +1 -0
- package/config/commands/init.md +1 -0
- package/config/commands/learn.md +1 -0
- package/config/commands/pr-review.md +1 -0
- package/config/commands/tailscale-serve.md +96 -0
- package/config/hooks/README.md +29 -0
- package/config/hooks/post-tool-use.md +16 -0
- package/config/hooks/pre-tool-use.md +16 -0
- package/config/opencode.json +52 -0
- package/config/opencode.json.template +52 -0
- package/config/rules/general.md +8 -0
- package/config/rules/git.md +11 -0
- package/config/rules/javascript.md +10 -0
- package/config/rules/python.md +10 -0
- package/config/rules/testing.md +10 -0
- package/config/skills/bizar/README.md +9 -0
- package/config/skills/bizar/SKILL.md +187 -0
- package/config/skills/cpp-coding-standards/README.md +28 -0
- package/config/skills/cpp-coding-standards/SKILL.md +634 -0
- package/config/skills/cpp-coding-standards/agents/openai.yaml +4 -0
- package/config/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/config/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/config/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/config/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/config/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/config/skills/cpp-testing/README.md +28 -0
- package/config/skills/cpp-testing/SKILL.md +304 -0
- package/config/skills/cpp-testing/agents/openai.yaml +4 -0
- package/config/skills/cpp-testing/references/coverage.md +370 -0
- package/config/skills/cpp-testing/references/framework-compare.md +175 -0
- package/config/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/config/skills/cpp-testing/references/mocking.md +364 -0
- package/config/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/config/skills/embedded-esp-idf/README.md +41 -0
- package/config/skills/embedded-esp-idf/SKILL.md +439 -0
- package/config/skills/embedded-esp-idf/agents/openai.yaml +4 -0
- package/config/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/config/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/config/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/config/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/config/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/config/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/config/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/config/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/config/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/config/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/config/skills/self-improvement/SKILL.md +64 -0
- package/package.json +47 -0
- package/templates/plan/htmx.min.js +1 -0
- package/templates/plan/library/bug-investigation.mdx +79 -0
- package/templates/plan/library/decision-record.mdx +71 -0
- package/templates/plan/library/feature-design.mdx +92 -0
- package/templates/plan/meta.json.template +8 -0
- package/templates/plan/plan.canvas.template +1711 -0
- package/templates/plan/plan.html.template +937 -0
- package/templates/plan/plan.mdx.template +46 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cpp-coding-standards
|
|
3
|
+
description: C++ coding standards skill for writing, reviewing, and refactoring modern C++17/20 code. Trigger this skill when editing or reviewing C++ source files, fixing C++ build errors, or applying modern C++ idioms (RAII, smart pointers, structured bindings, concepts, etc.).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# C++ Coding Standards
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill enforces modern C++17/20 best practices: memory safety via RAII and smart pointers, const correctness, value semantics, and structured concurrency. Load this skill whenever you write new C++ code, review existing C++ code, or debug C++ build errors.
|
|
11
|
+
|
|
12
|
+
**For deep dives see:**
|
|
13
|
+
- `references/memory-safety.md` — RAII, smart pointers, Rule of Five/Zero
|
|
14
|
+
- `references/modern-idioms.md` — C++17/20 features with examples
|
|
15
|
+
- `references/error-handling.md` — exceptions, error codes, `std::expected`
|
|
16
|
+
- `references/concurrency.md` — mutex, lock_guard, atomic, thread
|
|
17
|
+
- `references/review-checklist.md` — fast pre-commit checklist
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick-Start Checklist
|
|
22
|
+
|
|
23
|
+
**Always do these in C++:**
|
|
24
|
+
|
|
25
|
+
- [ ] **RAII everywhere** — every resource (memory, file handle, mutex) must have an owner object with a destructor
|
|
26
|
+
- [ ] **No raw `new`/`delete`** — use `std::unique_ptr` and `std::make_unique`
|
|
27
|
+
- [ ] **Prefer `std::vector`** over raw arrays; call `.reserve()` when size is known
|
|
28
|
+
- [ ] **No C-style casts** — use `static_cast`, `reinterpret_cast`, `const_cast`
|
|
29
|
+
- [ ] **No `using namespace std;`** in headers
|
|
30
|
+
- [ ] **Mark functions `noexcept`** when they cannot throw
|
|
31
|
+
- [ ] **Use `[[nodiscard]]`** on functions returning values callers must not ignore
|
|
32
|
+
- [ ] **Pass aggregates and non-trivial types by `const&`**, primitives by value
|
|
33
|
+
- [ ] **Const-correct from the start** — `const` everywhere data does not mutate
|
|
34
|
+
- [ ] **Use `override`/`final`** on virtual methods; avoid virtual when not needed
|
|
35
|
+
- [ ] **No virtual destructor without a polymorphic base** — a `virtual ~T() = default;` with no base class is a residual from an abandoned inheritance design; either remove the `virtual` or introduce a real base
|
|
36
|
+
- [ ] **No unused standard-library headers** — only `#include` what you use; embedded builds pay for every transitive include
|
|
37
|
+
- [ ] **No Variable Length Arrays (VLAs)** — `char buf[n];` is a GCC extension, not standard C++; use `std::vector<char>` or `std::unique_ptr<char[]>`
|
|
38
|
+
- [ ] **Lock mutexes with `std::lock_guard`** or `std::scoped_lock`
|
|
39
|
+
- [ ] **Initialize all variables** — prefer `= {}` initialization
|
|
40
|
+
- [ ] **Include what you use** — every `#include` must be needed
|
|
41
|
+
- [ ] **Apply `std::make_unique`/`std::make_shared`** over direct `new`
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Memory Safety
|
|
46
|
+
|
|
47
|
+
### RAII (Resource Acquisition Is Initialization)
|
|
48
|
+
|
|
49
|
+
Every resource must be owned by a object whose destructor releases it. Never call `delete` explicitly.
|
|
50
|
+
|
|
51
|
+
```cpp
|
|
52
|
+
// GOOD
|
|
53
|
+
void process_file(const std::string& path) {
|
|
54
|
+
std::ifstream in(path); // RAII: destructor closes file
|
|
55
|
+
// ... use in ...
|
|
56
|
+
} // file automatically closed
|
|
57
|
+
|
|
58
|
+
// BAD — error-prone, leak-prone
|
|
59
|
+
void process_file_bad(const std::string& path) {
|
|
60
|
+
std::ifstream* in = new std::ifstream(path);
|
|
61
|
+
// ... use *in ...
|
|
62
|
+
delete in; // easy to forget, early return bugs
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Smart Pointers
|
|
67
|
+
|
|
68
|
+
| Pointer Type | Ownership | Use When |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `std::unique_ptr<T>` | exclusive | Single owner, no sharing needed |
|
|
71
|
+
| `std::shared_ptr<T>` | shared | Shared ownership (use sparingly) |
|
|
72
|
+
| `std::weak_ptr<T>` | non-owning | Breaking cycles of `shared_ptr` |
|
|
73
|
+
|
|
74
|
+
```cpp
|
|
75
|
+
// unique_ptr — default choice
|
|
76
|
+
auto ptr = std::make_unique<MyClass>(args);
|
|
77
|
+
|
|
78
|
+
// shared_ptr — only when ownership must be shared
|
|
79
|
+
auto shared = std::make_shared<MyClass>(args);
|
|
80
|
+
|
|
81
|
+
// weak_ptr — to observe a shared_ptr without owning
|
|
82
|
+
std::weak_ptr<MyClass> observer = shared;
|
|
83
|
+
if (auto locked = observer.lock()) {
|
|
84
|
+
// use locked
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// BAD: raw ownership transfer
|
|
88
|
+
MyClass* raw = new MyClass(args);
|
|
89
|
+
// ...
|
|
90
|
+
delete raw;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Rule of Five / Rule of Zero
|
|
94
|
+
|
|
95
|
+
If you define any of: destructor, copy constructor, copy assignment, move constructor, move assignment — you likely need all five (Rule of Five). Otherwise, rely on the compiler-generated defaults (Rule of Zero).
|
|
96
|
+
|
|
97
|
+
```cpp
|
|
98
|
+
// Rule of Zero — prefer this
|
|
99
|
+
class Resource {
|
|
100
|
+
std::vector<int> data_;
|
|
101
|
+
public:
|
|
102
|
+
Resource() = default;
|
|
103
|
+
Resource(std::vector<int> d) : data_(std::move(d)) {}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Rule of Five — when you manage a raw resource
|
|
107
|
+
class RawOwner {
|
|
108
|
+
int* raw_;
|
|
109
|
+
public:
|
|
110
|
+
explicit RawOwner(size_t n) : raw_(new int[n]) {}
|
|
111
|
+
~RawOwner() { delete[] raw_; }
|
|
112
|
+
|
|
113
|
+
// Copy
|
|
114
|
+
RawOwner(const RawOwner& o) : raw_(new int[1]) { *raw_ = *o.raw_; }
|
|
115
|
+
RawOwner& operator=(const RawOwner& o) {
|
|
116
|
+
if (this != &o) { delete[] raw_; raw_ = new int[1]; *raw_ = *o.raw_; }
|
|
117
|
+
return *this;
|
|
118
|
+
}
|
|
119
|
+
// Move
|
|
120
|
+
RawOwner(RawOwner&& o) noexcept : raw_(std::exchange(o.raw_, nullptr)) {}
|
|
121
|
+
RawOwner& operator=(RawOwner&& o) noexcept {
|
|
122
|
+
if (this != &o) { delete[] raw_; raw_ = std::exchange(o.raw_, nullptr); }
|
|
123
|
+
return *this;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Const Correctness
|
|
131
|
+
|
|
132
|
+
Mark every function and parameter `const` when the operation does not modify observable state.
|
|
133
|
+
|
|
134
|
+
```cpp
|
|
135
|
+
class Calculator {
|
|
136
|
+
int state_ = 0;
|
|
137
|
+
public:
|
|
138
|
+
// Const member function — does not modify state_
|
|
139
|
+
int result() const { return state_; }
|
|
140
|
+
|
|
141
|
+
// Non-const — modifies state_
|
|
142
|
+
void add(int v) { state_ += v; }
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Const reference — callee cannot modify the argument
|
|
146
|
+
void print(const std::string& s) { std::cout << s; }
|
|
147
|
+
|
|
148
|
+
// BAD: missing const
|
|
149
|
+
int result() { return state_; } // should be int result() const
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Prefer `const` on member functions, on references and pointers to indicate non-modification, and on variables that should not change after initialization.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Modern C++17/20 Idioms
|
|
157
|
+
|
|
158
|
+
### Structured Bindings
|
|
159
|
+
|
|
160
|
+
```cpp
|
|
161
|
+
// Unpack tuples/pairs/structs
|
|
162
|
+
auto [key, value] = std::pair{1, std::string("hello")};
|
|
163
|
+
auto [a, b, c] = std::array{1, 2, 3};
|
|
164
|
+
|
|
165
|
+
// Use with maps
|
|
166
|
+
for (const auto& [k, v] : my_map) { /* ... */ }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `if constexpr`
|
|
170
|
+
|
|
171
|
+
Compile-time branch elimination — avoids instantiation of untaken branches.
|
|
172
|
+
|
|
173
|
+
```cpp
|
|
174
|
+
template <typename T>
|
|
175
|
+
auto process(const T& val) {
|
|
176
|
+
if constexpr (std::is_integral_v<T>) {
|
|
177
|
+
return val * 2;
|
|
178
|
+
} else if constexpr (std::is_floating_point_v<T>) {
|
|
179
|
+
return val * 2.0;
|
|
180
|
+
} else {
|
|
181
|
+
static_assert(sizeof(T) == 0, "Unsupported type");
|
|
182
|
+
return T{};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `std::optional`
|
|
188
|
+
|
|
189
|
+
Represent a value that may or may not be present.
|
|
190
|
+
|
|
191
|
+
```cpp
|
|
192
|
+
std::optional<int> find(const std::vector<int>& v, int target) {
|
|
193
|
+
for (auto it = v.begin(); it != v.end(); ++it) {
|
|
194
|
+
if (*it == target) return {static_cast<int>(std::distance(v.begin(), it))};
|
|
195
|
+
}
|
|
196
|
+
return std::nullopt;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Usage
|
|
200
|
+
if (auto idx = find(vec, 42)) {
|
|
201
|
+
std::cout << "Found at " << *idx;
|
|
202
|
+
} else {
|
|
203
|
+
std::cout << "Not found";
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `std::string_view`
|
|
208
|
+
|
|
209
|
+
Non-owning string slice — use for read-only string operations without copying.
|
|
210
|
+
|
|
211
|
+
```cpp
|
|
212
|
+
// GOOD: no allocation, no copy
|
|
213
|
+
void print_version(std::string_view prefix) {
|
|
214
|
+
std::cout << prefix << "1.2.3\n";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// BAD: forces allocation and copy
|
|
218
|
+
void print_version(const std::string& prefix) {
|
|
219
|
+
std::cout << prefix + "1.2.3\n"; // creates temporary string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Prefer string_view for function parameters that don't need ownership
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Ranges (C++20)
|
|
226
|
+
|
|
227
|
+
```cpp
|
|
228
|
+
#include <ranges>
|
|
229
|
+
|
|
230
|
+
// Pipeline style
|
|
231
|
+
auto evens = std::views::filter(v, [](int x) { return x % 2 == 0; });
|
|
232
|
+
auto doubled = evens | std::views::transform([](int x) { return x * 2; });
|
|
233
|
+
|
|
234
|
+
// Projections with std::sort
|
|
235
|
+
std::sort(vec.begin(), vec.end(), {}, &Node::priority); // sort by .priority
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Concepts (C++20)
|
|
239
|
+
|
|
240
|
+
Explicit constraints on template parameters — replaces SFINAE with readable syntax.
|
|
241
|
+
|
|
242
|
+
```cpp
|
|
243
|
+
template <typename T>
|
|
244
|
+
concept Numeric = std::integral<T> || std::floating_point<T>;
|
|
245
|
+
|
|
246
|
+
template <Numeric T>
|
|
247
|
+
T add(T a, T b) { return a + b; }
|
|
248
|
+
|
|
249
|
+
// Constrain containers
|
|
250
|
+
template <std::ranges::range R>
|
|
251
|
+
void print_all(const R& r) {
|
|
252
|
+
for (const auto& elem : r) std::cout << elem << '\n';
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Error Handling
|
|
259
|
+
|
|
260
|
+
### Decision Guide
|
|
261
|
+
|
|
262
|
+
| Scenario | Recommended Approach |
|
|
263
|
+
|---|---|
|
|
264
|
+
| Constructor failure that invalidates object | Exception |
|
|
265
|
+
| Expected failure (not found, invalid input) | `std::optional` or error code |
|
|
266
|
+
| Library-level expected-failure cases | `std::expected<T, E>` (C++23; use `tl::expected` in C++17/20) |
|
|
267
|
+
| Performance-critical hot paths | Error codes or `std::error_code` |
|
|
268
|
+
| No way to recover | Exception / `std::terminate` |
|
|
269
|
+
|
|
270
|
+
### `std::expected` (C++23)
|
|
271
|
+
|
|
272
|
+
```cpp
|
|
273
|
+
// C++23
|
|
274
|
+
std::expected<Config, std::error_code> load_config(const std::string& path) {
|
|
275
|
+
if (path.empty()) return std::unexpected(std::errc::invalid_argument);
|
|
276
|
+
return Config{};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Usage
|
|
280
|
+
auto cfg = load_config("app.cfg");
|
|
281
|
+
if (cfg) {
|
|
282
|
+
use(cfg.value());
|
|
283
|
+
} else {
|
|
284
|
+
handle_error(cfg.error());
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Exception Safety Levels
|
|
289
|
+
|
|
290
|
+
- **Nothrow** (`noexcept`): Operations that never throw — move constructors of `noexcept` types, swap, etc.
|
|
291
|
+
- **Strong**: Failed operation leaves program state unchanged (preferred for most code).
|
|
292
|
+
- **Basic**: Failed operation leaves object in a valid (but unspecified) state.
|
|
293
|
+
- **No guarantee**: Leaking, undefined behavior on failure — avoid.
|
|
294
|
+
|
|
295
|
+
```cpp
|
|
296
|
+
// Use noexcept sparingly — mark only functions that truly cannot fail
|
|
297
|
+
class Vector {
|
|
298
|
+
public:
|
|
299
|
+
void clear() noexcept { size_ = 0; } // cannot fail
|
|
300
|
+
// ...
|
|
301
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Casting
|
|
307
|
+
|
|
308
|
+
**Never use C-style casts.** They are too permissive (can cast away `const`, `reinterpret` pointers unsafely).
|
|
309
|
+
|
|
310
|
+
```cpp
|
|
311
|
+
// GOOD
|
|
312
|
+
double d = 3.14;
|
|
313
|
+
int i = static_cast<int>(d); // explicit numeric conversion
|
|
314
|
+
|
|
315
|
+
Base* base = new Derived;
|
|
316
|
+
Derived* derived = static_cast<Derived*>(base); // downcast (validate with dynamic_cast)
|
|
317
|
+
|
|
318
|
+
const Object* co = /* ... */;
|
|
319
|
+
Object* mutable_obj = const_cast<Object*>(co); // remove const (use with care)
|
|
320
|
+
|
|
321
|
+
// BAD — all of these are dangerous
|
|
322
|
+
int* p = (int*)some_void_ptr; // C-style: no type checking
|
|
323
|
+
int j = (int)d; // C-style: too easy to miss
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Use `dynamic_cast` for safe downcasts when runtime type checking is needed:
|
|
327
|
+
```cpp
|
|
328
|
+
Base* base = /* ... */;
|
|
329
|
+
Derived* derived = dynamic_cast<Derived*>(base);
|
|
330
|
+
if (derived) { /* use derived safely */ }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Headers
|
|
336
|
+
|
|
337
|
+
### Include What You Use
|
|
338
|
+
|
|
339
|
+
Every `#include` must be actively used. Unused includes cause slower compilation and hidden dependencies.
|
|
340
|
+
|
|
341
|
+
```cpp
|
|
342
|
+
// In foo.cpp — include only what's needed
|
|
343
|
+
#include <vector> // uses std::vector
|
|
344
|
+
#include <string> // uses std::string
|
|
345
|
+
#include "foo.h" // own header
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### No `using namespace std;` in Headers
|
|
349
|
+
|
|
350
|
+
Putting `using namespace std;` in a header pollutes the namespace for every translation unit that includes it.
|
|
351
|
+
|
|
352
|
+
```cpp
|
|
353
|
+
// BAD — header.h
|
|
354
|
+
using namespace std; // POLLUTES NAMESPACE
|
|
355
|
+
void helper(vector<int> v); // ambiguous: ours or std?
|
|
356
|
+
|
|
357
|
+
// GOOD — header.h
|
|
358
|
+
#include <vector>
|
|
359
|
+
void helper(const std::vector<int>& v); // explicit
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Prefer to also avoid `using` directives in `.cpp` files in namespace scope; it is acceptable in local scope.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Pass by Reference / Const-Ref
|
|
367
|
+
|
|
368
|
+
| Type | Recommended Passing |
|
|
369
|
+
|---|---|
|
|
370
|
+
| Built-in (`int`, `double`, pointer) | By value |
|
|
371
|
+
| Trivial struct (POD) | By value |
|
|
372
|
+
| Non-trivial class/struct | By `const&` |
|
|
373
|
+
| `std::string` | By `const&` |
|
|
374
|
+
| `std::vector` (out parameter) | By `&` (non-const ref) or return value |
|
|
375
|
+
| Callable (lambda, function) | By value or `auto&&` |
|
|
376
|
+
|
|
377
|
+
```cpp
|
|
378
|
+
// GOOD
|
|
379
|
+
void process(const std::string& input, std::vector<int>& output);
|
|
380
|
+
int primitive(int x);
|
|
381
|
+
|
|
382
|
+
// BAD
|
|
383
|
+
void process(std::string input); // unnecessary copy
|
|
384
|
+
void process(std::string& input); // invisible caller requirement + no rvalue support
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Containers
|
|
390
|
+
|
|
391
|
+
### `std::vector` — Default Choice
|
|
392
|
+
|
|
393
|
+
`std::vector` is the right default for a sequence. It has contiguous storage, good cache locality, and a complete standard library.
|
|
394
|
+
|
|
395
|
+
```cpp
|
|
396
|
+
// Reserve when size is known — avoids reallocations
|
|
397
|
+
std::vector<int> vec;
|
|
398
|
+
vec.reserve(1000); // allocates space for 1000, size still 0
|
|
399
|
+
for (int i = 0; i < 1000; ++i) vec.push_back(i);
|
|
400
|
+
|
|
401
|
+
// Initialize with size
|
|
402
|
+
std::vector<int> sized(100, 0); // 100 zeros
|
|
403
|
+
|
|
404
|
+
// Direct initialization
|
|
405
|
+
std::vector<int> init = {1, 2, 3, 4, 5};
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### `std::array` — Fixed-Size
|
|
409
|
+
|
|
410
|
+
Use `std::array<T, N>` for fixed-size, compile-time-sized arrays. Unlike raw arrays, it supports iterators and works with the standard library.
|
|
411
|
+
|
|
412
|
+
```cpp
|
|
413
|
+
// GOOD
|
|
414
|
+
std::array<int, 5> fixed = {1, 2, 3, 4, 5};
|
|
415
|
+
for (int x : fixed) { /* ... */ }
|
|
416
|
+
|
|
417
|
+
// BAD
|
|
418
|
+
int raw[5] = {1, 2, 3, 4, 5}; // raw array, decays to pointer
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### `std::map` / `std::unordered_map`
|
|
422
|
+
|
|
423
|
+
Use `std::map` when you need ordered keys or stable iterators. Use `std::unordered_map` when you need hash-table performance and don't need ordering.
|
|
424
|
+
|
|
425
|
+
```cpp
|
|
426
|
+
// Ordered — keys are sorted, O(log n) lookups
|
|
427
|
+
std::map<std::string, int> ordered;
|
|
428
|
+
|
|
429
|
+
// Unordered — O(1) average lookups, no ordering
|
|
430
|
+
std::unordered_map<std::string, int> hashmap;
|
|
431
|
+
hashmap.reserve(1000); // reserve buckets for performance
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Attributes
|
|
437
|
+
|
|
438
|
+
### `[[nodiscard]]`
|
|
439
|
+
|
|
440
|
+
Mark functions where ignoring the return value is a bug.
|
|
441
|
+
|
|
442
|
+
```cpp
|
|
443
|
+
[[nodiscard]] int compute();
|
|
444
|
+
[[nodiscard]] std::expected<int, E> risky_operation();
|
|
445
|
+
|
|
446
|
+
// BAD — compiler may warn on caller ignoring nodiscard result
|
|
447
|
+
compute(); // warning if compute() is [[nodiscard]]
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `noexcept`
|
|
451
|
+
|
|
452
|
+
Mark functions that cannot throw. This enables optimizations (move constructors become `noexcept` automatically when appropriate) and documents API contracts.
|
|
453
|
+
|
|
454
|
+
```cpp
|
|
455
|
+
class MoveOnly {
|
|
456
|
+
int* data_;
|
|
457
|
+
public:
|
|
458
|
+
MoveOnly() noexcept : data_(nullptr) {}
|
|
459
|
+
MoveOnly(MoveOnly&& o) noexcept : data_(std::exchange(o.data_, nullptr)) {}
|
|
460
|
+
MoveOnly& operator=(MoveOnly&&) noexcept { /* ... */ return *this; }
|
|
461
|
+
~MoveOnly() { delete[] data_; }
|
|
462
|
+
};
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### `override` / `final`
|
|
466
|
+
|
|
467
|
+
Use `override` on virtual methods that override a base class method. Use `final` on methods or classes that should not be further overridden.
|
|
468
|
+
|
|
469
|
+
```cpp
|
|
470
|
+
struct Base {
|
|
471
|
+
virtual void update() {}
|
|
472
|
+
virtual ~Base() = default;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
struct Derived : Base {
|
|
476
|
+
void update() override {} // explicitly overrides Base::update
|
|
477
|
+
void foo() final {} // Derived::foo cannot be overridden
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
struct FinalClass final {
|
|
481
|
+
// cannot be inherited
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Concurrency
|
|
488
|
+
|
|
489
|
+
### Mutex and Lock Guards
|
|
490
|
+
|
|
491
|
+
**Always** lock mutexes through `std::lock_guard` or `std::scoped_lock` (C++17). Never call `.lock()` and `.unlock()` directly — if an exception occurs between them, the mutex is permanently locked.
|
|
492
|
+
|
|
493
|
+
```cpp
|
|
494
|
+
#include <mutex>
|
|
495
|
+
|
|
496
|
+
std::mutex mtx;
|
|
497
|
+
int counter = 0;
|
|
498
|
+
|
|
499
|
+
// GOOD
|
|
500
|
+
void increment() {
|
|
501
|
+
std::lock_guard<std::mutex> lock(mtx); // RAII: unlocks on scope exit
|
|
502
|
+
++counter;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// BAD — exception-safe?
|
|
506
|
+
void increment_bad() {
|
|
507
|
+
mtx.lock();
|
|
508
|
+
++counter;
|
|
509
|
+
mtx.unlock(); // never called if ++counter throws
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### `std::scoped_lock` (C++17) — Multiple Mutexes
|
|
514
|
+
|
|
515
|
+
```cpp
|
|
516
|
+
std::mutex m1, m2;
|
|
517
|
+
// GOOD: locks both without deadlock
|
|
518
|
+
std::scoped_lock lock(m1, m2);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Atomics
|
|
522
|
+
|
|
523
|
+
Use `std::atomic` for simple shared counters, flags, and lock-free data structures. Prefer `std::atomic<T>` with trivial types.
|
|
524
|
+
|
|
525
|
+
```cpp
|
|
526
|
+
#include <atomic>
|
|
527
|
+
|
|
528
|
+
std::atomic<int> counter{0};
|
|
529
|
+
|
|
530
|
+
void increment() {
|
|
531
|
+
counter.fetch_add(1, std::memory_order_relaxed);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Simple bool flag
|
|
535
|
+
std::atomic<bool> done{false};
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### `std::thread` Lifecycle
|
|
539
|
+
|
|
540
|
+
- Always `.join()` or `.detach()` — a `std::thread` that is neither joined nor detached causes `std::terminate`.
|
|
541
|
+
- Prefer `std::jthread` (C++20) which auto-joins on destruction.
|
|
542
|
+
|
|
543
|
+
```cpp
|
|
544
|
+
// GOOD — explicit join
|
|
545
|
+
std::thread t([&]() { do_work(); });
|
|
546
|
+
// ... do other work ...
|
|
547
|
+
if (t.joinable()) t.join();
|
|
548
|
+
|
|
549
|
+
// BETTER (C++20) — jthread auto-joins
|
|
550
|
+
std::jthread jt([&](std::stop_token st) {
|
|
551
|
+
while (!st.stop_requested()) { do_work(); }
|
|
552
|
+
}); // automatically joins here
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Avoid Data Races
|
|
556
|
+
|
|
557
|
+
A data race is undefined behavior. Every shared variable must be:
|
|
558
|
+
1. Protected by a mutex (`std::lock_guard`), OR
|
|
559
|
+
2. Declared `std::atomic`, OR
|
|
560
|
+
3. Never modified concurrently (copy-on-write design)
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Common Anti-Patterns and Fixes
|
|
565
|
+
|
|
566
|
+
| Anti-Pattern | Fix |
|
|
567
|
+
|---|---|
|
|
568
|
+
| Raw `new`/`delete` | `std::unique_ptr` / `std::make_unique` |
|
|
569
|
+
| `using namespace std;` in header | Qualify names explicitly (`std::vector`) |
|
|
570
|
+
| Passing non-trivial types by value | Pass by `const&` |
|
|
571
|
+
| Ignoring `[[nodiscard]]` result | Fix the call site |
|
|
572
|
+
| C-style casts | `static_cast`, `reinterpret_cast`, `const_cast` |
|
|
573
|
+
| Locking a raw mutex without guard | `std::lock_guard` |
|
|
574
|
+
| Unused `#include`s | Remove or verify they're needed |
|
|
575
|
+
| Non-const member functions that don't modify state | Add `const` |
|
|
576
|
+
| `shared_ptr` for single ownership | `unique_ptr` |
|
|
577
|
+
| Virtual destructor missing | Add `virtual ~Base() = default;` |
|
|
578
|
+
| Uninitialized members | Use `= default` or member-initializer list |
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Do / Don't
|
|
583
|
+
|
|
584
|
+
### Do
|
|
585
|
+
|
|
586
|
+
```cpp
|
|
587
|
+
// DO: Use make_unique for single ownership
|
|
588
|
+
auto ptr = std::make_unique<Config>(filename);
|
|
589
|
+
|
|
590
|
+
// DO: Pass non-trivial types by const reference
|
|
591
|
+
void process(const std::vector<int>& data);
|
|
592
|
+
|
|
593
|
+
// DO: Use structured bindings
|
|
594
|
+
auto [id, name] = get_record();
|
|
595
|
+
|
|
596
|
+
// DO: Use [[nodiscard]] on important return values
|
|
597
|
+
[[nodiscard]] std::expected<int, Error> parse_number(std::string_view s);
|
|
598
|
+
|
|
599
|
+
// DO: Use override on virtual overrides
|
|
600
|
+
class Impl : public Base {
|
|
601
|
+
void run() override { /* ... */ }
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// DO: Reserve vector capacity when size is known
|
|
605
|
+
std::vector<double> vals;
|
|
606
|
+
vals.reserve(1000);
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Don't
|
|
610
|
+
|
|
611
|
+
```cpp
|
|
612
|
+
// DON'T: Use raw owning pointers
|
|
613
|
+
Config* cfg = new Config(filename);
|
|
614
|
+
delete cfg;
|
|
615
|
+
|
|
616
|
+
// DON'T: Use C-style casts
|
|
617
|
+
double d = 3.14;
|
|
618
|
+
int i = (int)d; // static_cast<int>(d) is explicit and searchable
|
|
619
|
+
|
|
620
|
+
// DON'T: Put using namespace std; in a header
|
|
621
|
+
// #include <vector>
|
|
622
|
+
// using namespace std; // NEVER in a header
|
|
623
|
+
|
|
624
|
+
// DON'T: Pass large objects by value (causes copy)
|
|
625
|
+
void print_big(std::string s); // copies! use const std::string&
|
|
626
|
+
|
|
627
|
+
// DON'T: Lock a mutex without lock_guard
|
|
628
|
+
mtx.lock();
|
|
629
|
+
// ... code that might throw ...
|
|
630
|
+
mtx.unlock(); // leaked if exception thrown above
|
|
631
|
+
|
|
632
|
+
// DON'T: Use shared_ptr for single, non-shared ownership
|
|
633
|
+
auto single = std::make_shared<Widget>(); // use unique_ptr instead
|
|
634
|
+
```
|