@pilllesss/yorn 1.0.182 → 1.0.183
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/README.md +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/skills/code-review/LICENSE +21 -0
- package/dist/skills/code-review/SKILL.md +233 -0
- package/dist/skills/code-review/assets/pr-review-template.md +137 -0
- package/dist/skills/code-review/assets/review-checklist.md +123 -0
- package/dist/skills/code-review/reference/angular.md +768 -0
- package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/dist/skills/code-review/reference/c.md +890 -0
- package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
- package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/dist/skills/code-review/reference/cpp.md +893 -0
- package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/dist/skills/code-review/reference/csharp.md +525 -0
- package/dist/skills/code-review/reference/css-less-sass.md +661 -0
- package/dist/skills/code-review/reference/dart.md +670 -0
- package/dist/skills/code-review/reference/django.md +985 -0
- package/dist/skills/code-review/reference/fastapi.md +580 -0
- package/dist/skills/code-review/reference/go.md +993 -0
- package/dist/skills/code-review/reference/java.md +409 -0
- package/dist/skills/code-review/reference/java8.md +586 -0
- package/dist/skills/code-review/reference/kotlin.md +1018 -0
- package/dist/skills/code-review/reference/nestjs.md +593 -0
- package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
- package/dist/skills/code-review/reference/php.md +684 -0
- package/dist/skills/code-review/reference/python.md +1073 -0
- package/dist/skills/code-review/reference/qt.md +757 -0
- package/dist/skills/code-review/reference/react.md +871 -0
- package/dist/skills/code-review/reference/ruby.md +964 -0
- package/dist/skills/code-review/reference/rust.md +846 -0
- package/dist/skills/code-review/reference/security-review-guide.md +494 -0
- package/dist/skills/code-review/reference/svelte.md +1064 -0
- package/dist/skills/code-review/reference/swift.md +936 -0
- package/dist/skills/code-review/reference/typescript.md +1016 -0
- package/dist/skills/code-review/reference/vue.md +924 -0
- package/dist/skills/code-review/reference/zig.md +440 -0
- package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
- package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
- package/dist/yorn.cjs +628 -628
- package/package.json +2 -2
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
# C++ Code Review Guide
|
|
2
|
+
|
|
3
|
+
> C++ code review guide focused on memory safety, lifetime, API design, modern features, and performance. Examples assume C++17/20/23.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Ownership and RAII](#ownership-and-raii)
|
|
8
|
+
- [Smart Pointer Selection Guide](#smart-pointer-selection-guide)
|
|
9
|
+
- [Lifetime and References](#lifetime-and-references)
|
|
10
|
+
- [Copy and Move Semantics](#copy-and-move-semantics)
|
|
11
|
+
- [Const-Correctness and API Design](#const-correctness-and-api-design)
|
|
12
|
+
- [Error Handling and Exception Safety](#error-handling-and-exception-safety)
|
|
13
|
+
- [Modern C++20/23 Features](#modern-c2023-features)
|
|
14
|
+
- [constexpr and consteval](#constexpr-and-consteval)
|
|
15
|
+
- [Concurrency](#concurrency)
|
|
16
|
+
- [Performance and Allocation](#performance-and-allocation)
|
|
17
|
+
- [Templates and Type Safety](#templates-and-type-safety)
|
|
18
|
+
- [Testing](#testing)
|
|
19
|
+
- [Tooling and Build Checks](#tooling-and-build-checks)
|
|
20
|
+
- [Review Checklist](#review-checklist)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Ownership and RAII
|
|
25
|
+
|
|
26
|
+
### Prefer RAII and smart pointers
|
|
27
|
+
|
|
28
|
+
Use RAII to express ownership. Default to `std::unique_ptr`, use `std::shared_ptr` only for shared lifetime.
|
|
29
|
+
|
|
30
|
+
```cpp
|
|
31
|
+
// ❌ Bad: manual new/delete with early returns
|
|
32
|
+
Foo* make_foo() {
|
|
33
|
+
Foo* foo = new Foo();
|
|
34
|
+
if (!foo->Init()) {
|
|
35
|
+
delete foo;
|
|
36
|
+
return nullptr;
|
|
37
|
+
}
|
|
38
|
+
return foo;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ✅ Good: RAII with unique_ptr
|
|
42
|
+
std::unique_ptr<Foo> make_foo() {
|
|
43
|
+
auto foo = std::make_unique<Foo>();
|
|
44
|
+
if (!foo->Init()) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
return foo;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Wrap C resources
|
|
52
|
+
|
|
53
|
+
```cpp
|
|
54
|
+
// ✅ Good: wrap FILE* with unique_ptr
|
|
55
|
+
using FilePtr = std::unique_ptr<FILE, decltype(&fclose)>;
|
|
56
|
+
|
|
57
|
+
FilePtr open_file(const char* path) {
|
|
58
|
+
return FilePtr(fopen(path, "rb"), &fclose);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### RAII best practices
|
|
63
|
+
|
|
64
|
+
```cpp
|
|
65
|
+
// ✅ Good: RAII wrapper for POSIX file descriptors
|
|
66
|
+
class Fd {
|
|
67
|
+
int fd_ = -1;
|
|
68
|
+
public:
|
|
69
|
+
explicit Fd(int fd) : fd_(fd) {}
|
|
70
|
+
~Fd() { if (fd_ >= 0) ::close(fd_); }
|
|
71
|
+
|
|
72
|
+
Fd(const Fd&) = delete;
|
|
73
|
+
Fd& operator=(const Fd&) = delete;
|
|
74
|
+
Fd(Fd&& o) noexcept : fd_(std::exchange(o.fd_, -1)) {}
|
|
75
|
+
Fd& operator=(Fd&& o) noexcept {
|
|
76
|
+
if (this != &o) {
|
|
77
|
+
if (fd_ >= 0) ::close(fd_);
|
|
78
|
+
fd_ = std::exchange(o.fd_, -1);
|
|
79
|
+
}
|
|
80
|
+
return *this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
int get() const { return fd_; }
|
|
84
|
+
int release() { return std::exchange(fd_, -1); }
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Never mix ownership styles
|
|
89
|
+
|
|
90
|
+
```cpp
|
|
91
|
+
// ❌ Bad: raw new + container of raw pointers — who deletes?
|
|
92
|
+
std::vector<Widget*> widgets;
|
|
93
|
+
widgets.push_back(new Widget());
|
|
94
|
+
// When is delete called? Unclear.
|
|
95
|
+
|
|
96
|
+
// ✅ Good: container of unique_ptr
|
|
97
|
+
std::vector<std::unique_ptr<Widget>> widgets;
|
|
98
|
+
widgets.push_back(std::make_unique<Widget>());
|
|
99
|
+
// Automatically cleaned up when vector is destroyed.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Smart Pointer Selection Guide
|
|
105
|
+
|
|
106
|
+
### Decision matrix
|
|
107
|
+
|
|
108
|
+
| Scenario | Pointer | Why |
|
|
109
|
+
|----------|---------|-----|
|
|
110
|
+
| Single owner | `unique_ptr` | Zero overhead, clear ownership |
|
|
111
|
+
| Shared ownership (few owners) | `shared_ptr` | Reference counted, thread-safe refcount |
|
|
112
|
+
| Non-owning observer | `weak_ptr` | Breaks cycles, checks liveness |
|
|
113
|
+
| Never-null reference | raw reference `T&` | No ownership, cannot be null |
|
|
114
|
+
| Maybe-null observer | raw pointer `T*` | No ownership, can be null |
|
|
115
|
+
|
|
116
|
+
### Avoid shared_ptr when unique_ptr suffices
|
|
117
|
+
|
|
118
|
+
```cpp
|
|
119
|
+
// ❌ Bad: unnecessary shared ownership
|
|
120
|
+
class Window {
|
|
121
|
+
std::shared_ptr<Renderer> renderer_;
|
|
122
|
+
public:
|
|
123
|
+
Window() : renderer_(std::make_shared<Renderer>()) {}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ✅ Good: sole owner uses unique_ptr
|
|
127
|
+
class Window {
|
|
128
|
+
std::unique_ptr<Renderer> renderer_;
|
|
129
|
+
public:
|
|
130
|
+
Window() : renderer_(std::make_unique<Renderer>()) {}
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Break cycles with weak_ptr
|
|
135
|
+
|
|
136
|
+
```cpp
|
|
137
|
+
// ❌ Bad: cycle → memory leak
|
|
138
|
+
struct Node {
|
|
139
|
+
std::shared_ptr<Node> parent;
|
|
140
|
+
std::shared_ptr<Node> child;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ✅ Good: weak_ptr breaks the back-reference
|
|
144
|
+
struct Node {
|
|
145
|
+
std::weak_ptr<Node> parent; // non-owning back-reference
|
|
146
|
+
std::shared_ptr<Node> child; // owning forward-reference
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Lifetime and References
|
|
153
|
+
|
|
154
|
+
### Avoid dangling references and views
|
|
155
|
+
|
|
156
|
+
`std::string_view` and `std::span` do not own data. Make sure the owner outlives the view.
|
|
157
|
+
|
|
158
|
+
```cpp
|
|
159
|
+
// ❌ Bad: returning string_view to a temporary
|
|
160
|
+
std::string_view bad_view() {
|
|
161
|
+
std::string s = make_name();
|
|
162
|
+
return s; // dangling
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ✅ Good: return owning string
|
|
166
|
+
std::string good_name() {
|
|
167
|
+
return make_name();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ✅ Good: view tied to caller-owned data
|
|
171
|
+
std::string_view good_view(const std::string& s) {
|
|
172
|
+
return s;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Lambda captures
|
|
177
|
+
|
|
178
|
+
```cpp
|
|
179
|
+
// ❌ Bad: capture reference that escapes
|
|
180
|
+
std::function<void()> make_task() {
|
|
181
|
+
int value = 42;
|
|
182
|
+
return [&]() { use(value); }; // dangling
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ✅ Good: capture by value
|
|
186
|
+
std::function<void()> make_task() {
|
|
187
|
+
int value = 42;
|
|
188
|
+
return [value]() { use(value); };
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Beware of temporary lifetime extension pitfalls
|
|
193
|
+
|
|
194
|
+
```cpp
|
|
195
|
+
// ❌ Bad: reference bound to temporary that is destroyed
|
|
196
|
+
const std::string& name = get_name(); // temporary destroyed at end of statement
|
|
197
|
+
use(name); // dangling reference
|
|
198
|
+
|
|
199
|
+
// ✅ Good: store the value
|
|
200
|
+
std::string name = get_name();
|
|
201
|
+
use(name);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Copy and Move Semantics
|
|
207
|
+
|
|
208
|
+
### Rule of 0/3/5
|
|
209
|
+
|
|
210
|
+
Prefer the Rule of 0 by using RAII types. If you own a resource, define or delete copy and move operations.
|
|
211
|
+
|
|
212
|
+
```cpp
|
|
213
|
+
// ❌ Bad: raw ownership with default copy
|
|
214
|
+
struct Buffer {
|
|
215
|
+
int* data;
|
|
216
|
+
size_t size;
|
|
217
|
+
explicit Buffer(size_t n) : data(new int[n]), size(n) {}
|
|
218
|
+
~Buffer() { delete[] data; }
|
|
219
|
+
// copy ctor/assign are implicitly generated -> double delete
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ✅ Good: Rule of 0 with std::vector
|
|
223
|
+
struct Buffer {
|
|
224
|
+
std::vector<int> data;
|
|
225
|
+
explicit Buffer(size_t n) : data(n) {}
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Delete unwanted copies
|
|
230
|
+
|
|
231
|
+
```cpp
|
|
232
|
+
struct Socket {
|
|
233
|
+
Socket() = default;
|
|
234
|
+
~Socket() { close(); }
|
|
235
|
+
|
|
236
|
+
Socket(const Socket&) = delete;
|
|
237
|
+
Socket& operator=(const Socket&) = delete;
|
|
238
|
+
Socket(Socket&&) noexcept = default;
|
|
239
|
+
Socket& operator=(Socket&&) noexcept = default;
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Use std::move explicitly
|
|
244
|
+
|
|
245
|
+
```cpp
|
|
246
|
+
// ❌ Bad: copies instead of moves
|
|
247
|
+
std::string name = get_name();
|
|
248
|
+
data_.push_back(name); // copy
|
|
249
|
+
|
|
250
|
+
// ✅ Good: move when source is no longer needed
|
|
251
|
+
std::string name = get_name();
|
|
252
|
+
data_.push_back(std::move(name));
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Const-Correctness and API Design
|
|
258
|
+
|
|
259
|
+
### Use const and explicit
|
|
260
|
+
|
|
261
|
+
```cpp
|
|
262
|
+
class User {
|
|
263
|
+
public:
|
|
264
|
+
const std::string& name() const { return name_; }
|
|
265
|
+
void set_name(std::string name) { name_ = std::move(name); }
|
|
266
|
+
|
|
267
|
+
private:
|
|
268
|
+
std::string name_;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
struct Millis {
|
|
272
|
+
explicit Millis(int v) : value(v) {}
|
|
273
|
+
int value;
|
|
274
|
+
};
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Avoid object slicing
|
|
278
|
+
|
|
279
|
+
```cpp
|
|
280
|
+
struct Shape { virtual ~Shape() = default; };
|
|
281
|
+
struct Circle : Shape { void draw() const; };
|
|
282
|
+
|
|
283
|
+
// ❌ Bad: slices Circle into Shape
|
|
284
|
+
void draw(Shape shape);
|
|
285
|
+
|
|
286
|
+
// ✅ Good: pass by reference
|
|
287
|
+
void draw(const Shape& shape);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Use override and final
|
|
291
|
+
|
|
292
|
+
```cpp
|
|
293
|
+
struct Base {
|
|
294
|
+
virtual void run() = 0;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
struct Worker final : Base {
|
|
298
|
+
void run() override {}
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Error Handling and Exception Safety
|
|
305
|
+
|
|
306
|
+
### Prefer RAII for cleanup
|
|
307
|
+
|
|
308
|
+
```cpp
|
|
309
|
+
// ✅ Good: RAII handles cleanup on exceptions
|
|
310
|
+
void process() {
|
|
311
|
+
std::vector<int> data = load_data(); // safe cleanup
|
|
312
|
+
do_work(data);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Do not throw from destructors
|
|
317
|
+
|
|
318
|
+
```cpp
|
|
319
|
+
struct File {
|
|
320
|
+
~File() noexcept { close(); }
|
|
321
|
+
void close();
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Use expected results for normal failures
|
|
326
|
+
|
|
327
|
+
```cpp
|
|
328
|
+
// ✅ C++23: std::expected
|
|
329
|
+
#include <expected>
|
|
330
|
+
|
|
331
|
+
std::expected<int, ParseError> parse_int(std::string_view s) {
|
|
332
|
+
try {
|
|
333
|
+
return std::stoi(std::string(s));
|
|
334
|
+
} catch (const std::invalid_argument&) {
|
|
335
|
+
return std::unexpected(ParseError::InvalidFormat);
|
|
336
|
+
} catch (const std::out_of_range&) {
|
|
337
|
+
return std::unexpected(ParseError::OutOfRange);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ✅ Pre-C++23: std::optional
|
|
342
|
+
std::optional<int> parse_int(const std::string& s) {
|
|
343
|
+
try {
|
|
344
|
+
return std::stoi(s);
|
|
345
|
+
} catch (...) {
|
|
346
|
+
return std::nullopt;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Exception safety levels
|
|
352
|
+
|
|
353
|
+
- **No-throw guarantee**: `noexcept` — destructors, swap, move operations.
|
|
354
|
+
- **Strong guarantee**: operation either succeeds or state is unchanged. Use copy-and-swap idiom.
|
|
355
|
+
- **Basic guarantee**: on exception, no resources leaked, object in valid (but possibly modified) state.
|
|
356
|
+
|
|
357
|
+
```cpp
|
|
358
|
+
// ✅ Good: strong guarantee via copy-and-swap
|
|
359
|
+
void Container::push_back(const Item& item) {
|
|
360
|
+
Container tmp(*this); // copy
|
|
361
|
+
tmp.push_back_impl(item); // may throw, but tmp is a copy
|
|
362
|
+
swap(*this, tmp); // noexcept swap
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Modern C++20/23 Features
|
|
369
|
+
|
|
370
|
+
### Concepts (C++20)
|
|
371
|
+
|
|
372
|
+
```cpp
|
|
373
|
+
// ❌ Bad: SFINAE boilerplate
|
|
374
|
+
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
|
375
|
+
T gcd(T a, T b) {
|
|
376
|
+
while (b) { a %= b; std::swap(a, b); }
|
|
377
|
+
return a;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ✅ Good: concepts are readable and composable
|
|
381
|
+
template <std::integral T>
|
|
382
|
+
T gcd(T a, T b) {
|
|
383
|
+
while (b) { a %= b; std::swap(a, b); }
|
|
384
|
+
return a;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ✅ Define custom concepts
|
|
388
|
+
template <typename T>
|
|
389
|
+
concept Printable = requires(T t, std::ostream& os) {
|
|
390
|
+
{ os << t } -> std::convertible_to<std::ostream&>;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
void log(const Printable auto& value) {
|
|
394
|
+
std::cout << "[LOG] " << value << '\n';
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Ranges and views (C++20)
|
|
399
|
+
|
|
400
|
+
```cpp
|
|
401
|
+
#include <ranges>
|
|
402
|
+
#include <vector>
|
|
403
|
+
#include <numeric>
|
|
404
|
+
|
|
405
|
+
// ✅ Good: composable range pipelines
|
|
406
|
+
std::vector<int> scores = {85, 92, 67, 73, 98, 55};
|
|
407
|
+
|
|
408
|
+
auto top_scores = scores
|
|
409
|
+
| std::views::filter([](int s) { return s >= 80; })
|
|
410
|
+
| std::views::transform([](int s) { return s * 1.1; }) // bonus
|
|
411
|
+
| std::views::take(3);
|
|
412
|
+
|
|
413
|
+
// Iterate without allocating intermediate containers
|
|
414
|
+
for (double s : top_scores) {
|
|
415
|
+
std::cout << s << ' ';
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Modules (C++20)
|
|
420
|
+
|
|
421
|
+
```cpp
|
|
422
|
+
// ✅ Module interface unit (math.cppm)
|
|
423
|
+
export module math;
|
|
424
|
+
|
|
425
|
+
export int add(int a, int b) { return a + b; }
|
|
426
|
+
export constexpr double pi = 3.14159265358979;
|
|
427
|
+
|
|
428
|
+
// ✅ Module implementation unit (math_impl.cpp)
|
|
429
|
+
module math;
|
|
430
|
+
|
|
431
|
+
int internal_helper() { /* not exported */ }
|
|
432
|
+
|
|
433
|
+
// Consumer:
|
|
434
|
+
import math;
|
|
435
|
+
int result = add(1, 2);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Review note**: Modules are still maturing in tooling support. Check that your build system (CMake 3.28+, MSVC 17.x, Clang 16+) supports them before adopting. Headers remain the safe default.
|
|
439
|
+
|
|
440
|
+
### Deducing this (C++23)
|
|
441
|
+
|
|
442
|
+
```cpp
|
|
443
|
+
// ❌ Bad: verbose CRTP for static polymorphism
|
|
444
|
+
template <typename Derived>
|
|
445
|
+
struct Base {
|
|
446
|
+
void call() { static_cast<Derived*>(this)->impl(); }
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// ✅ Good: C++23 explicit object parameter
|
|
450
|
+
struct Widget {
|
|
451
|
+
template <typename Self>
|
|
452
|
+
void log(this Self&& self) {
|
|
453
|
+
// self is Widget& or Widget&& depending on call context
|
|
454
|
+
std::cout << self.name << '\n';
|
|
455
|
+
}
|
|
456
|
+
std::string name;
|
|
457
|
+
};
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## constexpr and consteval
|
|
463
|
+
|
|
464
|
+
### When to use constexpr vs consteval
|
|
465
|
+
|
|
466
|
+
- `constexpr`: can be evaluated at compile time *or* runtime.
|
|
467
|
+
- `consteval`: **must** be evaluated at compile time (immediate function).
|
|
468
|
+
|
|
469
|
+
```cpp
|
|
470
|
+
// ✅ constexpr: compile-time when possible, runtime otherwise
|
|
471
|
+
constexpr int factorial(int n) {
|
|
472
|
+
int result = 1;
|
|
473
|
+
for (int i = 2; i <= n; ++i) result *= i;
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
constexpr int c = factorial(5); // compile-time
|
|
478
|
+
int r = factorial(argc); // runtime
|
|
479
|
+
|
|
480
|
+
// ✅ consteval: enforce compile-time evaluation
|
|
481
|
+
consteval int forced_compiletime(int n) {
|
|
482
|
+
return n * n;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
constexpr int v = forced_compiletime(42); // OK
|
|
486
|
+
// int v2 = forced_compiletime(argc); // ERROR: not a constant expression
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Compile-time computation for performance
|
|
490
|
+
|
|
491
|
+
```cpp
|
|
492
|
+
// ✅ Good: lookup table generated at compile time
|
|
493
|
+
constexpr auto make_crc_table() {
|
|
494
|
+
std::array<uint32_t, 256> table{};
|
|
495
|
+
for (uint32_t i = 0; i < 256; ++i) {
|
|
496
|
+
uint32_t crc = i;
|
|
497
|
+
for (int j = 0; j < 8; ++j) {
|
|
498
|
+
crc = (crc >> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
|
499
|
+
}
|
|
500
|
+
table[i] = crc;
|
|
501
|
+
}
|
|
502
|
+
return table;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static constexpr auto crc_table = make_crc_table();
|
|
506
|
+
|
|
507
|
+
// Use at runtime with zero initialization cost
|
|
508
|
+
uint32_t crc32(const uint8_t* data, size_t len) {
|
|
509
|
+
uint32_t crc = 0xFFFFFFFF;
|
|
510
|
+
for (size_t i = 0; i < len; ++i) {
|
|
511
|
+
crc = (crc >> 8) ^ crc_table[(crc ^ data[i]) & 0xFF];
|
|
512
|
+
}
|
|
513
|
+
return ~crc;
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### constinit for guaranteed static initialization
|
|
518
|
+
|
|
519
|
+
```cpp
|
|
520
|
+
// ✅ Good: prevent static initialization order fiasco
|
|
521
|
+
constinit int global_counter = 0; // guaranteed static init, not dynamic
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Concurrency
|
|
527
|
+
|
|
528
|
+
### Protect shared data
|
|
529
|
+
|
|
530
|
+
```cpp
|
|
531
|
+
// ❌ Bad: data race
|
|
532
|
+
int counter = 0;
|
|
533
|
+
void inc() { counter++; }
|
|
534
|
+
|
|
535
|
+
// ✅ Good: atomic
|
|
536
|
+
std::atomic<int> counter{0};
|
|
537
|
+
void inc() { counter.fetch_add(1, std::memory_order_relaxed); }
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Use RAII locks
|
|
541
|
+
|
|
542
|
+
```cpp
|
|
543
|
+
std::mutex mu;
|
|
544
|
+
std::vector<int> data;
|
|
545
|
+
|
|
546
|
+
void add(int v) {
|
|
547
|
+
std::lock_guard<std::mutex> lock(mu);
|
|
548
|
+
data.push_back(v);
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Prefer std::jthread over std::thread (C++20)
|
|
553
|
+
|
|
554
|
+
```cpp
|
|
555
|
+
// ❌ Bad: std::thread requires manual join
|
|
556
|
+
void run() {
|
|
557
|
+
std::thread t([]{ do_work(); });
|
|
558
|
+
// forgot to join → std::terminate
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ✅ Good: jthread joins automatically on destruction
|
|
562
|
+
void run() {
|
|
563
|
+
std::jthread t([](std::stop_token st) {
|
|
564
|
+
while (!st.stop_requested()) {
|
|
565
|
+
do_work();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
// automatically joined; stop token enables cooperative cancellation
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Structured concurrency with std::execution (future C++26)
|
|
573
|
+
|
|
574
|
+
Note: As of C++23, use `std::jthread` + `std::stop_token` for cooperative cancellation. The `std::execution` library (P2300) is expected in C++26.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Performance and Allocation
|
|
579
|
+
|
|
580
|
+
### Avoid repeated allocations
|
|
581
|
+
|
|
582
|
+
```cpp
|
|
583
|
+
// ❌ Bad: repeated reallocation
|
|
584
|
+
std::vector<int> build(int n) {
|
|
585
|
+
std::vector<int> out;
|
|
586
|
+
for (int i = 0; i < n; ++i) {
|
|
587
|
+
out.push_back(i);
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ✅ Good: reserve upfront
|
|
593
|
+
std::vector<int> build(int n) {
|
|
594
|
+
std::vector<int> out;
|
|
595
|
+
out.reserve(static_cast<size_t>(n));
|
|
596
|
+
for (int i = 0; i < n; ++i) {
|
|
597
|
+
out.push_back(i);
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### String concatenation
|
|
604
|
+
|
|
605
|
+
```cpp
|
|
606
|
+
// ❌ Bad: repeated allocation
|
|
607
|
+
std::string join(const std::vector<std::string>& parts) {
|
|
608
|
+
std::string out;
|
|
609
|
+
for (const auto& p : parts) {
|
|
610
|
+
out += p;
|
|
611
|
+
}
|
|
612
|
+
return out;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ✅ Good: reserve total size
|
|
616
|
+
std::string join(const std::vector<std::string>& parts) {
|
|
617
|
+
size_t total = 0;
|
|
618
|
+
for (const auto& p : parts) {
|
|
619
|
+
total += p.size();
|
|
620
|
+
}
|
|
621
|
+
std::string out;
|
|
622
|
+
out.reserve(total);
|
|
623
|
+
for (const auto& p : parts) {
|
|
624
|
+
out += p;
|
|
625
|
+
}
|
|
626
|
+
return out;
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Small Buffer Optimization (SBO)
|
|
631
|
+
|
|
632
|
+
```cpp
|
|
633
|
+
// ✅ Good: avoid heap for small data
|
|
634
|
+
void process(const char* name) {
|
|
635
|
+
// Use stack for short names, heap only for long ones
|
|
636
|
+
std::string buf;
|
|
637
|
+
buf.reserve(64); // typically stays on stack via SSO
|
|
638
|
+
buf = name;
|
|
639
|
+
// ...
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Use std::span for zero-copy views
|
|
644
|
+
|
|
645
|
+
```cpp
|
|
646
|
+
// ❌ Bad: copies the vector
|
|
647
|
+
void process(std::vector<int> data);
|
|
648
|
+
|
|
649
|
+
// ✅ Good: non-owning view, works with vector, array, C array
|
|
650
|
+
void process(std::span<const int> data);
|
|
651
|
+
|
|
652
|
+
std::vector<int> v = {1, 2, 3};
|
|
653
|
+
process(v); // no copy
|
|
654
|
+
int arr[] = {4, 5, 6};
|
|
655
|
+
process(arr); // no copy
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## Templates and Type Safety
|
|
661
|
+
|
|
662
|
+
### Prefer constrained templates (C++20)
|
|
663
|
+
|
|
664
|
+
```cpp
|
|
665
|
+
// ❌ Bad: overly generic
|
|
666
|
+
template <typename T>
|
|
667
|
+
T add(T a, T b) {
|
|
668
|
+
return a + b;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// ✅ Good: constrained
|
|
672
|
+
template <typename T>
|
|
673
|
+
requires std::is_integral_v<T>
|
|
674
|
+
T add(T a, T b) {
|
|
675
|
+
return a + b;
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Use static_assert for invariants
|
|
680
|
+
|
|
681
|
+
```cpp
|
|
682
|
+
template <typename T>
|
|
683
|
+
struct Packet {
|
|
684
|
+
static_assert(std::is_trivially_copyable_v<T>,
|
|
685
|
+
"Packet payload must be trivially copyable");
|
|
686
|
+
T payload;
|
|
687
|
+
};
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Avoid template bloat
|
|
691
|
+
|
|
692
|
+
```cpp
|
|
693
|
+
// ❌ Bad: full template instantiation for each T, even if only one method varies
|
|
694
|
+
template <typename T>
|
|
695
|
+
class Service {
|
|
696
|
+
void connect() { /* 100 lines of identical code */ }
|
|
697
|
+
void process(T item) { /* type-specific */ }
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// ✅ Good: factor out type-independent code into a non-template base
|
|
701
|
+
class ServiceBase {
|
|
702
|
+
protected:
|
|
703
|
+
void connect() { /* 100 lines of shared code */ }
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
template <typename T>
|
|
707
|
+
class Service : public ServiceBase {
|
|
708
|
+
void process(T item) { /* type-specific */ }
|
|
709
|
+
};
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Testing
|
|
715
|
+
|
|
716
|
+
### Framework selection
|
|
717
|
+
|
|
718
|
+
| Framework | Best For |
|
|
719
|
+
|-----------|----------|
|
|
720
|
+
| **Google Test (GTest)** | Large projects, CI, GMock integration |
|
|
721
|
+
| **Catch2** | Header-only, BDD-style, modern C++ |
|
|
722
|
+
| **doctest** | Lightweight, single-header, fast compile |
|
|
723
|
+
|
|
724
|
+
### Google Test basics
|
|
725
|
+
|
|
726
|
+
```cpp
|
|
727
|
+
#include <gtest/gtest.h>
|
|
728
|
+
#include "parser.h"
|
|
729
|
+
|
|
730
|
+
TEST(ParserTest, EmptyInputReturnsNull) {
|
|
731
|
+
auto token = parse("");
|
|
732
|
+
EXPECT_EQ(token, nullptr);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
TEST(ParserTest, ValidInteger) {
|
|
736
|
+
auto token = parse("42");
|
|
737
|
+
ASSERT_NE(token, nullptr);
|
|
738
|
+
EXPECT_EQ(token->type, TokenType::Int);
|
|
739
|
+
EXPECT_EQ(token->value, 42);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
TEST(ParserTest, NegativeNumber) {
|
|
743
|
+
auto token = parse("-7");
|
|
744
|
+
ASSERT_NE(token, nullptr);
|
|
745
|
+
EXPECT_EQ(token->value, -7);
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Test fixtures
|
|
750
|
+
|
|
751
|
+
```cpp
|
|
752
|
+
class DatabaseTest : public ::testing::Test {
|
|
753
|
+
protected:
|
|
754
|
+
void SetUp() override {
|
|
755
|
+
db_ = std::make_unique<Database>(":memory:");
|
|
756
|
+
db_->execute("CREATE TABLE users (id INTEGER, name TEXT)");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
void TearDown() override {
|
|
760
|
+
db_.reset();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
std::unique_ptr<Database> db_;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
TEST_F(DatabaseTest, InsertAndQuery) {
|
|
767
|
+
db_->execute("INSERT INTO users VALUES (1, 'Alice')");
|
|
768
|
+
auto rows = db_->query("SELECT * FROM users");
|
|
769
|
+
ASSERT_EQ(rows.size(), 1);
|
|
770
|
+
EXPECT_EQ(rows[0].get<std::string>("name"), "Alice");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
TEST_F(DatabaseTest, EmptyTableReturnsNoRows) {
|
|
774
|
+
auto rows = db_->query("SELECT * FROM users");
|
|
775
|
+
EXPECT_TRUE(rows.empty());
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### Mock objects with GMock
|
|
780
|
+
|
|
781
|
+
```cpp
|
|
782
|
+
#include <gmock/gmock.h>
|
|
783
|
+
|
|
784
|
+
class HttpClient {
|
|
785
|
+
public:
|
|
786
|
+
virtual ~HttpClient() = default;
|
|
787
|
+
virtual HttpResponse get(const std::string& url) = 0;
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
class MockHttpClient : public HttpClient {
|
|
791
|
+
public:
|
|
792
|
+
MOCK_METHOD(HttpResponse, get, (const std::string& url), (override));
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
TEST(UserServiceTest, FetchesUserProfile) {
|
|
796
|
+
MockHttpClient client;
|
|
797
|
+
EXPECT_CALL(client, get("https://api.example.com/user/1"))
|
|
798
|
+
.WillOnce(Return(HttpResponse{200, R"({"name":"Alice"})"}));
|
|
799
|
+
|
|
800
|
+
UserService svc(&client);
|
|
801
|
+
auto profile = svc.get_profile(1);
|
|
802
|
+
EXPECT_EQ(profile.name, "Alice");
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Test exception safety
|
|
807
|
+
|
|
808
|
+
```cpp
|
|
809
|
+
TEST(AllocatorTest, ThrowsOnOverflow) {
|
|
810
|
+
EXPECT_THROW(allocate(SIZE_MAX), std::bad_alloc);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
TEST(AllocatorTest, NoLeakOnException) {
|
|
814
|
+
// Run under ASan to verify no leaks when exception is thrown
|
|
815
|
+
try {
|
|
816
|
+
auto buf = allocate(1024);
|
|
817
|
+
throw std::runtime_error("simulated failure");
|
|
818
|
+
} catch (...) {
|
|
819
|
+
// ASan will catch any leaks
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
## Tooling and Build Checks
|
|
827
|
+
|
|
828
|
+
```bash
|
|
829
|
+
# Warnings
|
|
830
|
+
clang++ -Wall -Wextra -Werror -Wconversion -Wshadow -std=c++20 ...
|
|
831
|
+
|
|
832
|
+
# Sanitizers (debug builds)
|
|
833
|
+
clang++ -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
|
|
834
|
+
clang++ -fsanitize=thread -fno-omit-frame-pointer -g ...
|
|
835
|
+
|
|
836
|
+
# Static analysis
|
|
837
|
+
clang-tidy src/*.cpp -- -std=c++20
|
|
838
|
+
|
|
839
|
+
# Formatting
|
|
840
|
+
clang-format -i src/*.cpp include/*.h
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Recommended compiler flags for safety
|
|
844
|
+
|
|
845
|
+
```bash
|
|
846
|
+
# Strict mode for new code
|
|
847
|
+
clang++ -std=c++20 -Wall -Wextra -Werror -Wshadow -Wconversion \
|
|
848
|
+
-Wsign-conversion -Wold-style-cast -Wnon-virtual-dtor \
|
|
849
|
+
-Woverloaded-virtual -Wnull-dereference -Wformat=2 \
|
|
850
|
+
-fsanitize=address,undefined -fno-omit-frame-pointer
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
## Review Checklist
|
|
856
|
+
|
|
857
|
+
### Safety and Lifetime
|
|
858
|
+
- [ ] Ownership is explicit (RAII, unique_ptr by default)
|
|
859
|
+
- [ ] No dangling references or views
|
|
860
|
+
- [ ] Rule of 0/3/5 followed for resource-owning types
|
|
861
|
+
- [ ] No raw new/delete in business logic
|
|
862
|
+
- [ ] Destructors are noexcept and do not throw
|
|
863
|
+
- [ ] Smart pointer types match ownership semantics (unique vs shared vs weak)
|
|
864
|
+
- [ ] No shared_ptr cycles (use weak_ptr for back-references)
|
|
865
|
+
|
|
866
|
+
### API and Design
|
|
867
|
+
- [ ] const-correctness is applied consistently
|
|
868
|
+
- [ ] Constructors are explicit where needed
|
|
869
|
+
- [ ] Override/final used for virtual functions
|
|
870
|
+
- [ ] No object slicing (pass by ref or pointer)
|
|
871
|
+
- [ ] Concepts constrain template parameters (C++20)
|
|
872
|
+
|
|
873
|
+
### Modern Features
|
|
874
|
+
- [ ] constexpr used for compile-time computation where beneficial
|
|
875
|
+
- [ ] Ranges preferred over manual loops for data pipelines (C++20)
|
|
876
|
+
- [ ] std::jthread preferred over std::thread for new code (C++20)
|
|
877
|
+
- [ ] std::expected used for error handling (C++23) where available
|
|
878
|
+
|
|
879
|
+
### Concurrency
|
|
880
|
+
- [ ] Shared data is protected (mutex or atomics)
|
|
881
|
+
- [ ] Locking order is consistent
|
|
882
|
+
- [ ] No blocking while holding locks
|
|
883
|
+
|
|
884
|
+
### Performance
|
|
885
|
+
- [ ] Unnecessary allocations avoided (reserve, move, span)
|
|
886
|
+
- [ ] Copies avoided in hot paths
|
|
887
|
+
- [ ] Algorithmic complexity is reasonable
|
|
888
|
+
|
|
889
|
+
### Testing and Tooling
|
|
890
|
+
- [ ] Unit tests cover happy path, error paths, and edge cases
|
|
891
|
+
- [ ] Builds clean with warnings enabled
|
|
892
|
+
- [ ] Sanitizers run on critical code paths
|
|
893
|
+
- [ ] Static analysis (clang-tidy) results are addressed
|