@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,320 @@
|
|
|
1
|
+
# Concurrency in Modern C++
|
|
2
|
+
|
|
3
|
+
This reference covers mutexes, atomics, threads, and common concurrency patterns.
|
|
4
|
+
|
|
5
|
+
## Core Principle: No Data Races
|
|
6
|
+
|
|
7
|
+
A **data race** is simultaneous, unsynchronized access to the same memory location where at least one access is a write. It is **undefined behavior**.
|
|
8
|
+
|
|
9
|
+
Every shared variable must be:
|
|
10
|
+
1. Protected by a mutex (`std::lock_guard`), OR
|
|
11
|
+
2. Declared `std::atomic`, OR
|
|
12
|
+
3. Never modified concurrently (read-only after initialization)
|
|
13
|
+
|
|
14
|
+
## Mutex and Lock Guards
|
|
15
|
+
|
|
16
|
+
### `std::lock_guard` — Simplest Guard
|
|
17
|
+
|
|
18
|
+
RAII lock — unlocks on scope exit.
|
|
19
|
+
|
|
20
|
+
```cpp
|
|
21
|
+
#include <mutex>
|
|
22
|
+
|
|
23
|
+
std::mutex mtx;
|
|
24
|
+
int counter = 0;
|
|
25
|
+
|
|
26
|
+
void increment() {
|
|
27
|
+
std::lock_guard<std::mutex> lock(mtx); // acquires lock
|
|
28
|
+
++counter; // safe: only one thread here at a time
|
|
29
|
+
} // destructor releases lock
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `std::unique_lock` — Flexible Guard
|
|
33
|
+
|
|
34
|
+
Supports deferred locking, timed locking, and manual unlock.
|
|
35
|
+
|
|
36
|
+
```cpp
|
|
37
|
+
std::mutex mtx;
|
|
38
|
+
std::unique_lock<std::mutex> lock(mtx); // locks immediately
|
|
39
|
+
lock.unlock(); // temporarily unlock
|
|
40
|
+
lock.lock(); // relock
|
|
41
|
+
// ...
|
|
42
|
+
// automatically unlocks when lock goes out of scope
|
|
43
|
+
|
|
44
|
+
// Deferred — lock is not acquired yet
|
|
45
|
+
std::unique_lock<std::mutex> deferred(mtx, std::defer_lock);
|
|
46
|
+
// ... other code ...
|
|
47
|
+
deferred.lock(); // now lock
|
|
48
|
+
deferred.unlock(); // unlock early if needed
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `std::scoped_lock` (C++17) — Multiple Mutexes
|
|
52
|
+
|
|
53
|
+
Locks multiple mutexes without deadlock (uses the deadlock-avoidance algorithm).
|
|
54
|
+
|
|
55
|
+
```cpp
|
|
56
|
+
std::mutex m1, m2, m3;
|
|
57
|
+
|
|
58
|
+
// GOOD: locks all three safely — avoids deadlock
|
|
59
|
+
std::scoped_lock lock(m1, m2, m3);
|
|
60
|
+
// ... access protected data ...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### The Deadlock Problem
|
|
64
|
+
|
|
65
|
+
Lock mutexes in a **fixed order** if using multiple `lock_guard`/`unique_lock`. Or prefer `scoped_lock`.
|
|
66
|
+
|
|
67
|
+
```cpp
|
|
68
|
+
// DANGEROUS: different threads lock in opposite orders → deadlock
|
|
69
|
+
void transfer(Account& from, Account& to, int amount) {
|
|
70
|
+
std::lock_guard<std::mutex> l1(from.mtx);
|
|
71
|
+
std::lock_guard<std::mutex> l2(to.mtx); // deadlock if another thread does to→from
|
|
72
|
+
from.balance -= amount;
|
|
73
|
+
to.balance += amount;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// SAFE: scoped_lock locks both simultaneously
|
|
77
|
+
void transfer(Account& from, Account& to, int amount) {
|
|
78
|
+
std::scoped_lock lock(from.mtx, to.mtx); // no deadlock
|
|
79
|
+
from.balance -= amount;
|
|
80
|
+
to.balance += amount;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Atomics
|
|
85
|
+
|
|
86
|
+
Use `std::atomic` for simple shared values where the type supports lock-free operations.
|
|
87
|
+
|
|
88
|
+
```cpp
|
|
89
|
+
#include <atomic>
|
|
90
|
+
|
|
91
|
+
// Basic types
|
|
92
|
+
std::atomic<int> counter{0};
|
|
93
|
+
counter.fetch_add(1); // returns old value
|
|
94
|
+
counter++; // simple ops also work
|
|
95
|
+
int old = counter.exchange(42); // read and write
|
|
96
|
+
|
|
97
|
+
// Memory ordering
|
|
98
|
+
counter.store(1, std::memory_order_relaxed); // only use for counters
|
|
99
|
+
counter.store(1, std::memory_order_release); // release semantics
|
|
100
|
+
int x = counter.load(std::memory_order_acquire); // acquire semantics
|
|
101
|
+
|
|
102
|
+
// Default memory_order_seq_cst is safest and the default — use it unless you
|
|
103
|
+
// have measured that relaxed/acq_rel gives meaningful gains
|
|
104
|
+
|
|
105
|
+
// Boolean flag
|
|
106
|
+
std::atomic<bool> ready{false};
|
|
107
|
+
void producer() { ready.store(true, std::memory_order_release); }
|
|
108
|
+
void consumer() {
|
|
109
|
+
while (!ready.load(std::memory_order_acquire)) {
|
|
110
|
+
std::this_thread::yield();
|
|
111
|
+
}
|
|
112
|
+
// consume
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## `std::atomic` with User-Defined Types
|
|
117
|
+
|
|
118
|
+
A `std::atomic<T>` requires `T` to be trivially copyable and lock-free for the operations to be available.
|
|
119
|
+
|
|
120
|
+
```cpp
|
|
121
|
+
struct Point { int x; int y; };
|
|
122
|
+
|
|
123
|
+
// std::atomic<Point> — only works if Point is trivially copyable and
|
|
124
|
+
// the platform provides lock-free atomics for its size
|
|
125
|
+
static_assert(std::is_trivially_copyable_v<Point>);
|
|
126
|
+
static_assert(std::atomic<Point>::is_always_lock_free());
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Threads
|
|
130
|
+
|
|
131
|
+
### `std::thread` Lifecycle — Critical Rule
|
|
132
|
+
|
|
133
|
+
**Every `std::thread` must be either joined (`.join()`) or detached (`.detach()`) before destruction.** A `std::thread` that is neither joined nor detached calls `std::terminate`.
|
|
134
|
+
|
|
135
|
+
```cpp
|
|
136
|
+
#include <thread>
|
|
137
|
+
|
|
138
|
+
void background_task(int param) { /* ... */ }
|
|
139
|
+
|
|
140
|
+
// GOOD — explicit join
|
|
141
|
+
std::thread t(background_task, 42);
|
|
142
|
+
t.join(); // wait for completion
|
|
143
|
+
|
|
144
|
+
// GOOD — detached (fire and forget, but lose ability to synchronize)
|
|
145
|
+
std::thread t(background_task, 42);
|
|
146
|
+
t.detach(); // thread continues running independently
|
|
147
|
+
|
|
148
|
+
// DANGEROUS — terminate() called
|
|
149
|
+
{
|
|
150
|
+
std::thread t(background_task, 42);
|
|
151
|
+
// t not joined or detached here
|
|
152
|
+
} // std::terminate() called
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `std::jthread` (C++20) — Auto-Join
|
|
156
|
+
|
|
157
|
+
Automatically joins on destruction. Prefer `std::jthread` over `std::thread` in C++20.
|
|
158
|
+
|
|
159
|
+
```cpp
|
|
160
|
+
#include <thread>
|
|
161
|
+
|
|
162
|
+
void cancellable_work(std::stop_token token) {
|
|
163
|
+
while (!token.stop_requested()) {
|
|
164
|
+
do_step();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
std::jthread jt(cancellable_work);
|
|
169
|
+
// ... work happens in background ...
|
|
170
|
+
// jt destroyed here → automatically requests stop and joins
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Stop Tokens (C++20)
|
|
174
|
+
|
|
175
|
+
Gracefully stop a `jthread` without polling a flag.
|
|
176
|
+
|
|
177
|
+
```cpp
|
|
178
|
+
void long_task(std::stop_token token) {
|
|
179
|
+
for (size_t i = 0; i < 1000; ++i) {
|
|
180
|
+
if (token.stop_requested()) {
|
|
181
|
+
std::cout << "Stopped at " << i << '\n';
|
|
182
|
+
return; // clean exit
|
|
183
|
+
}
|
|
184
|
+
compute_step(i);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
std::jthread worker(long_task);
|
|
189
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
190
|
+
worker.request_stop(); // signals stop_requested()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## `std::condition_variable`
|
|
194
|
+
|
|
195
|
+
Used to block a thread until another thread signals that a condition is true.
|
|
196
|
+
|
|
197
|
+
```cpp
|
|
198
|
+
#include <condition_variable>
|
|
199
|
+
#include <queue>
|
|
200
|
+
|
|
201
|
+
std::mutex mtx;
|
|
202
|
+
std::condition_variable cv;
|
|
203
|
+
std::queue<int> q;
|
|
204
|
+
|
|
205
|
+
void producer() {
|
|
206
|
+
for (int i = 0; i < 10; ++i) {
|
|
207
|
+
{
|
|
208
|
+
std::lock_guard<std::mutex> lock(mtx);
|
|
209
|
+
q.push(i);
|
|
210
|
+
}
|
|
211
|
+
cv.notify_one(); // wake one waiting consumer
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
void consumer() {
|
|
216
|
+
while (true) {
|
|
217
|
+
std::unique_lock<std::mutex> lock(mtx);
|
|
218
|
+
cv.wait(lock, [&] { return !q.empty() || /* done signal */; });
|
|
219
|
+
if (q.empty()) break; // producer done
|
|
220
|
+
int val = q.front();
|
|
221
|
+
q.pop();
|
|
222
|
+
lock.unlock(); // unlock while processing (optional)
|
|
223
|
+
// process val
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## `std::future` and `std::promise`
|
|
229
|
+
|
|
230
|
+
One-shot communication from a background thread to a caller.
|
|
231
|
+
|
|
232
|
+
```cpp
|
|
233
|
+
#include <future>
|
|
234
|
+
|
|
235
|
+
std::promise<int> p;
|
|
236
|
+
std::future<int> f = p.get_future();
|
|
237
|
+
|
|
238
|
+
std::thread t([&p]() {
|
|
239
|
+
// compute result
|
|
240
|
+
p.set_value(42);
|
|
241
|
+
// or on error: p.set_exception(std::make_exception_ptr(std::runtime_error("fail")));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
int result = f.get(); // blocks until value is set
|
|
245
|
+
t.join();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `std::async` — Simpler Parallelism
|
|
249
|
+
|
|
250
|
+
Launch a task asynchronously and get a future.
|
|
251
|
+
|
|
252
|
+
```cpp
|
|
253
|
+
#include <future>
|
|
254
|
+
|
|
255
|
+
// LaunchPolicy::async = run in separate thread
|
|
256
|
+
// LaunchPolicy::deferred = lazy evaluation in calling thread
|
|
257
|
+
auto fut = std::async(std::launch::async, []() {
|
|
258
|
+
return compute_expensive_result();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
int result = fut.get(); // blocks until ready
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Common Concurrency Mistakes
|
|
265
|
+
|
|
266
|
+
```cpp
|
|
267
|
+
// WRONG: Forgetting to protect shared data
|
|
268
|
+
std::string shared; // data race: multiple threads write
|
|
269
|
+
void writer() { shared = "hello"; }
|
|
270
|
+
void reader() { std::cout << shared; }
|
|
271
|
+
std::thread t1(writer), t2(reader); // DATA RACE
|
|
272
|
+
|
|
273
|
+
// RIGHT: Protect with mutex
|
|
274
|
+
std::mutex mtx;
|
|
275
|
+
std::string shared;
|
|
276
|
+
void writer() {
|
|
277
|
+
std::lock_guard<std::mutex> lock(mtx);
|
|
278
|
+
shared = "hello";
|
|
279
|
+
}
|
|
280
|
+
void reader() {
|
|
281
|
+
std::lock_guard<std::mutex> lock(mtx);
|
|
282
|
+
std::cout << shared;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// WRONG: Passing references to local variables to threads
|
|
286
|
+
void bad() {
|
|
287
|
+
std::string s = "data";
|
|
288
|
+
std::thread t([&s]() { use(s); }); // reference to local — DANGER
|
|
289
|
+
t.detach(); // s destroyed, thread still uses it
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// RIGHT: Move or copy into the thread
|
|
293
|
+
void good() {
|
|
294
|
+
std::string s = "data";
|
|
295
|
+
std::thread t([s]() { use(s); }); // copy into thread's closure
|
|
296
|
+
t.join(); // or jthread
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// WRONG: Locking a mutex and calling an unknown function (potential deadlock)
|
|
300
|
+
// If the function tries to lock the same mutex → deadlock
|
|
301
|
+
void dangerous(std::mutex& m) {
|
|
302
|
+
std::lock_guard<std::mutex> lock(m);
|
|
303
|
+
call_unknown_function(); // might lock m again
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Summary Table
|
|
308
|
+
|
|
309
|
+
| Primitive | Use When |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `std::mutex` | Protect a single variable or small critical section |
|
|
312
|
+
| `std::lock_guard` | Simple RAII lock (most common) |
|
|
313
|
+
| `std::unique_lock` | Deferred/timed locking, condition variables |
|
|
314
|
+
| `std::scoped_lock` | Locking multiple mutexes at once |
|
|
315
|
+
| `std::atomic` | Simple shared counters, flags, lock-free data |
|
|
316
|
+
| `std::thread` | Create a thread (remember join/detach) |
|
|
317
|
+
| `std::jthread` | Thread that should auto-join and support stop tokens |
|
|
318
|
+
| `std::condition_variable` | Block until a condition is signaled |
|
|
319
|
+
| `std::future`/`std::promise` | One-shot result from background task |
|
|
320
|
+
| `std::async` | Simple parallel task without explicit thread management |
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Error Handling in Modern C++
|
|
2
|
+
|
|
3
|
+
This reference covers when to use exceptions, error codes, or `std::expected`.
|
|
4
|
+
|
|
5
|
+
## Decision Guide
|
|
6
|
+
|
|
7
|
+
| Scenario | Recommended Approach |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Constructor failure that leaves object in invalid state | Exception |
|
|
10
|
+
| Expected failure (element not found, invalid input format) | `std::optional`, error code, or `std::expected` |
|
|
11
|
+
| Library with many failure modes | `std::error_code` or `std::expected<T, E>` |
|
|
12
|
+
| Performance-critical hot path where exceptions are unacceptable | Error codes |
|
|
13
|
+
| Impossible failure (e.g., `std::terminate`) | Let it crash |
|
|
14
|
+
| Memory allocation failure | `std::bad_alloc` exception (let it propagate) |
|
|
15
|
+
|
|
16
|
+
**Golden rule:** Throw exceptions for *exceptional* conditions — things that should not happen in normal operation and from which the caller cannot reasonably recover. Do not use exceptions for expected failure cases (file not found, invalid user input).
|
|
17
|
+
|
|
18
|
+
## Exceptions
|
|
19
|
+
|
|
20
|
+
### Throwing and Catching
|
|
21
|
+
|
|
22
|
+
```cpp
|
|
23
|
+
#include <stdexcept>
|
|
24
|
+
|
|
25
|
+
class ConfigError : public std::runtime_error {
|
|
26
|
+
public:
|
|
27
|
+
explicit ConfigError(const std::string& msg) : std::runtime_error(msg) {}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
void load_config(const std::string& path) {
|
|
31
|
+
std::ifstream in(path);
|
|
32
|
+
if (!in) {
|
|
33
|
+
throw ConfigError("Cannot open config: " + path);
|
|
34
|
+
}
|
|
35
|
+
// ...
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Caller
|
|
39
|
+
try {
|
|
40
|
+
load_config("app.cfg");
|
|
41
|
+
} catch (const ConfigError& e) {
|
|
42
|
+
std::cerr << "Config error: " << e.what() << '\n';
|
|
43
|
+
} catch (const std::exception& e) {
|
|
44
|
+
std::cerr << "Unexpected: " << e.what() << '\n';
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Constructor Failure
|
|
49
|
+
|
|
50
|
+
Prefer to signal constructor failure via:
|
|
51
|
+
1. A `static` factory function that returns `std::optional` or `std::expected`
|
|
52
|
+
2. Throwing an exception (acceptable if construction failure is truly exceptional)
|
|
53
|
+
|
|
54
|
+
```cpp
|
|
55
|
+
// Approach 1: Factory returning optional
|
|
56
|
+
class Widget {
|
|
57
|
+
Widget(int param) {} // private or non-public
|
|
58
|
+
public:
|
|
59
|
+
static std::optional<Widget> create(int param) {
|
|
60
|
+
if (param < 0) return std::nullopt;
|
|
61
|
+
return Widget(param);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Approach 2: Exception for truly unexpected failure
|
|
66
|
+
class NetworkConnection {
|
|
67
|
+
public:
|
|
68
|
+
NetworkConnection(const std::string& host, int port) {
|
|
69
|
+
if (port < 0 || port > 65535)
|
|
70
|
+
throw std::invalid_argument("Invalid port: " + std::to_string(port));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Error Codes
|
|
76
|
+
|
|
77
|
+
Traditional C-style error handling via return values. Works well in performance-sensitive code and for system-level APIs.
|
|
78
|
+
|
|
79
|
+
```cpp
|
|
80
|
+
#include <system_error>
|
|
81
|
+
|
|
82
|
+
enum class MyError { NotFound = 1, InvalidInput, Timeout };
|
|
83
|
+
|
|
84
|
+
// Return std::error_code — idiomatic C++ error code
|
|
85
|
+
std::error_code read_data(const std::string& path, std::vector<char>& out) {
|
|
86
|
+
std::ifstream in(path, std::ios::binary);
|
|
87
|
+
if (!in) return std::errc::no_such_file_or_directory;
|
|
88
|
+
out.assign(std::istreambuf_iterator<char>(in), {});
|
|
89
|
+
return {}; // empty error_code = success
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Usage
|
|
93
|
+
std::vector<char> data;
|
|
94
|
+
std::error_code ec = read_data("file.bin", data);
|
|
95
|
+
if (ec) {
|
|
96
|
+
std::cerr << "Read failed: " << ec.message() << '\n';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// use data
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## `std::expected<T, E>` (C++23)
|
|
103
|
+
|
|
104
|
+
The preferred way to handle expected failures in modern C++. Available in C++23; use the `std::expected` proposal or the `tl::expected` library for C++17/20.
|
|
105
|
+
|
|
106
|
+
```cpp
|
|
107
|
+
// C++23 std::expected
|
|
108
|
+
#include <expected>
|
|
109
|
+
|
|
110
|
+
std::expected<int, std::error_code> parse_int(std::string_view s) {
|
|
111
|
+
int result = 0;
|
|
112
|
+
for (char c : s) {
|
|
113
|
+
if (!std::isdigit(c))
|
|
114
|
+
return std::unexpected(std::errc::invalid_argument);
|
|
115
|
+
result = result * 10 + (c - '0');
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Usage
|
|
121
|
+
auto opt = parse_int("123abc");
|
|
122
|
+
if (opt) {
|
|
123
|
+
std::cout << "Value: " << *opt << '\n';
|
|
124
|
+
} else {
|
|
125
|
+
std::cerr << "Parse failed: " << opt.error().message() << '\n';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Transform with .and_then (monadic interface in C++23)
|
|
129
|
+
auto doubled = parse_int("42").transform([](int x) { return x * 2; });
|
|
130
|
+
// doubled == 84
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Monadic Operations (C++23)
|
|
134
|
+
|
|
135
|
+
```cpp
|
|
136
|
+
// and_then — chain operations that can fail
|
|
137
|
+
auto result = read_config()
|
|
138
|
+
.and_then([](const Config& c) -> std::expected<Settings, E> {
|
|
139
|
+
return validate(c);
|
|
140
|
+
})
|
|
141
|
+
.transform([](const Settings& s) {
|
|
142
|
+
return process(s); // successful transform
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// or_else — handle failure
|
|
146
|
+
auto with_fallback = read_config().or_else([](auto) {
|
|
147
|
+
return read_default_config();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// transform_error — map error type
|
|
151
|
+
auto with_err_msg = parse_int("abc").transform_error([](std::errc e) {
|
|
152
|
+
return std::string(std::strerror(std::make_error_code(e).value()));
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## `std::optional` for Expected Absence
|
|
157
|
+
|
|
158
|
+
When the only "failure" is that a value is not present, `std::optional` is the right tool.
|
|
159
|
+
|
|
160
|
+
```cpp
|
|
161
|
+
std::optional<int> find_user(const std::string& name) {
|
|
162
|
+
for (auto& [n, id] : user_db) {
|
|
163
|
+
if (n == name) return {id};
|
|
164
|
+
}
|
|
165
|
+
return std::nullopt;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (auto uid = find_user("Bob")) {
|
|
169
|
+
std::cout << "User ID: " << *uid << '\n';
|
|
170
|
+
} else {
|
|
171
|
+
std::cout << "User not found\n";
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Exception Safety Guarantees
|
|
176
|
+
|
|
177
|
+
Four levels of exception safety:
|
|
178
|
+
|
|
179
|
+
| Level | Guarantee | Use When |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| **Nothrow** (`noexcept`) | Operation never throws | Move constructors, swap, essential operations |
|
|
182
|
+
| **Strong** | Failed op leaves state unchanged | Most business logic |
|
|
183
|
+
| **Basic** | Failed op leaves object valid | Destructors, `operator=` |
|
|
184
|
+
| **No guarantee** | Any behavior on failure | Avoid |
|
|
185
|
+
|
|
186
|
+
```cpp
|
|
187
|
+
class Widget {
|
|
188
|
+
std::vector<int> data_;
|
|
189
|
+
public:
|
|
190
|
+
// Strong exception safety — copy-on-write pattern
|
|
191
|
+
void replace(std::vector<int> new_data) {
|
|
192
|
+
std::vector<int> tmp = std::move(new_data); // copy into temp
|
|
193
|
+
data_.swap(tmp); // noexcept swap
|
|
194
|
+
// old data_ is in tmp, destroyed when tmp goes out of scope
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// noexcept on functions that cannot fail
|
|
198
|
+
void clear() noexcept { data_.clear(); }
|
|
199
|
+
bool empty() const noexcept { return data_.empty(); }
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## noexcept — When to Use
|
|
204
|
+
|
|
205
|
+
Use `noexcept` on functions that **cannot** throw by design. This enables optimizations and documents API contracts.
|
|
206
|
+
|
|
207
|
+
```cpp
|
|
208
|
+
// Destructors should be noexcept (the default)
|
|
209
|
+
class Resource {
|
|
210
|
+
int* ptr_ = nullptr;
|
|
211
|
+
public:
|
|
212
|
+
~Resource() { delete[] ptr_; } // implicitly noexcept
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Move constructors of containers are noexcept — this matters
|
|
216
|
+
// A vector will use move() instead of copy() if the move ctor is noexcept
|
|
217
|
+
struct Important {
|
|
218
|
+
std::vector<int> big_data;
|
|
219
|
+
Important(Important&&) noexcept = default; // ensure noexcept
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Mark observers noexcept
|
|
223
|
+
template <typename T>
|
|
224
|
+
bool is_empty(const std::vector<T>& v) noexcept {
|
|
225
|
+
return v.empty(); // cannot throw — .empty() is noexcept
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Do NOT mark a function `noexcept` if it can throw.** Misleading `noexcept` violates caller expectations and can cause `std::terminate` to be called unexpectedly.
|