You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8485c35
Browse filesBrowse the repository at this point in the historyBrowse files
perf: make interface destructors protected and non-virtual (#361)
Every toolbox interface declared `virtual ~I() = default`. The library
and its embedded users never destroy an object through an interface
pointer, but the virtual destructor still puts a deleting destructor
in each implementation's vtable. That destructor calls operator
delete, which links malloc/free into firmware that never allocates.
Under multiple inheritance it also adds one thunk per base.
All 24 library interfaces now declare `~I() = default` in a protected
section, as EMIL does. SynchronousPidController gets one too, so the
derived interface does not reopen a public non-virtual destructor.
Deleting through an interface pointer is now a compile error.
Compiled at -O2, a translation unit that instantiates
Lqr<float, 2, 1, 300> used to emit two deleting destructors and a
reference to operator delete. It now emits neither.
Code that owned windows through std::unique_ptr<Window> holds them
differently now:
- TestWindowing holds each window on the stack.
- The FFT and PSD simulators pick a static, stateless window by
reference.
The simulator's own Plant interface keeps its virtual destructor: it
is host-only and owned through std::unique_ptr<Plant>.
The rule is updated in AGENTS.md, CLAUDE.md,
.github/copilot-instructions.md, the reviewer agents and the C++
instructions.
Closes#359
Claude-Session: https://claude.ai/code/session_01Q53m7MZDmHwocmKbhhd2o5
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claude/agents/reviewer.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ End with totals + verdict: APPROVE / REQUEST CHANGES.
48
48
-[ ] SOLID: one concern per class, constructor injection, depend on abstractions, no duplicated logic.
49
49
50
50
**Interfaces & errors (WARNING)**
51
-
-[ ]`virtual ~I() = default` — never `= 0`. No exceptions — `std::optional`/status enums.
51
+
-[ ]Interfaces: `protected:` non-virtual `~I() = default` — no `virtual` or `= 0` destructor, no ownership or deletion through an interface pointer. No exceptions — `std::optional`/status enums.
52
52
53
53
**Testing (WARNING)**
54
54
-[ ]`TEST_F` on `float` — no `TYPED_TEST`, no multi-type, never plain `TEST()`.
Copy file name to clipboardExpand all lines: .github/agents/reviewer.agent.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,7 +54,7 @@ End with totals + verdict: APPROVE / REQUEST CHANGES.
54
54
-[ ] SOLID: one concern per class, constructor injection, depend on abstractions, no duplicated logic.
55
55
56
56
**Interfaces & errors (WARNING)**
57
-
-[ ]`virtual ~I() = default` — never `= 0`. No exceptions — `std::optional`/status enums.
57
+
-[ ]Interfaces: `protected:` non-virtual `~I() = default` — no `virtual` or `= 0` destructor, no ownership or deletion through an interface pointer. No exceptions — `std::optional`/status enums.
58
58
59
59
**Testing (WARNING)**
60
60
-[ ]`TEST_F` on `float` — no `TYPED_TEST`, no multi-type, never plain `TEST()`.
Copy file name to clipboardExpand all lines: .github/copilot-instructions.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,5 +9,5 @@ Essentials (full detail in AGENTS.md):
9
9
-**Embedded** — no `#pragma GCC optimize` / `optimize` attribute (they block inlining); the consumer sets optimization flags per TU; `OPTIMIZE_FOR_SPEED` (forced inlining) on hot paths.
Copy file name to clipboardExpand all lines: .github/instructions/numerical-cpp.instructions.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -68,7 +68,7 @@ Use `math::IsFinite` for finiteness checks: a consumer may still build with `-ff
68
68
- SOLID principles — constructor injection, depend on abstractions
69
69
- DRY — extract shared logic into helpers or templates
70
70
- RAII for resource management
71
-
-**No pure virtual destructors**: Never declare `virtual ~Interface()= 0`. Pure virtual destructors force the compiler to emit a thunk and a separate body, wasting memory on embedded targets. Use `virtual ~Interface() = default` if a virtual destructor is required, or omit it entirely when the class is never deleted through a base pointer.
71
+
-**Protected, non-virtual interface destructors**: declare `~Interface() = default` in a `protected:` section, never `virtual ~Interface()` (defaulted or `= 0`). A virtual destructor puts a deleting destructor in every implementation's vtable, which links `operator delete` (and with it `malloc`/`free`) into heap-free firmware, and under multiple inheritance adds a thunk per base. The protected destructor makes deletion through an interface pointer a compile error. Concrete classes declare no destructor unless they need one, and callers hold the concrete type rather than a `std::unique_ptr` to the interface.
72
72
- No virtual calls in ISR-callable or real-time critical paths
- Interfaces = pure virtual classes with a `protected:`, non-virtual `~I() = default` — **never** a `virtual` or `= 0` destructor. A virtual one puts a deleting destructor in every vtable, which links `operator delete`/`free` into heap-free firmware.
48
+
- Concrete classes declare no destructor unless they need one. Never own or delete through an interface pointer (no `std::unique_ptr<I>`): hold the concrete type, pass `I&`.
48
49
- No exceptions. Use `std::optional<T>` or status enums. `assert`/`really_assert` for preconditions.
49
50
- SOLID + DIP: constructor injection; depend on abstractions. RAII. No virtual calls in real-time paths.
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,5 +9,5 @@ Essentials (full detail in AGENTS.md):
9
9
-**Embedded** — no `#pragma GCC optimize` / `optimize` attribute (they block inlining); the consumer sets optimization flags per TU; `OPTIMIZE_FOR_SPEED` (forced inlining) on hot paths.
0 commit comments