Repository navigation
Description
Problem
The toolbox's rule (AGENTS.md:47, CLAUDE.md, .github/copilot-instructions.md) is "interfaces virtual ~I() = default", and 21 classes follow it. Nothing in the library, or in its embedded users, destroys an object through an interface pointer, because the library is heap-free. A virtual destructor still makes the compiler:
- emit a deleting destructor (D0) for every concrete class and put it in the vtable, so it is never garbage-collected;
- call
operator deletefrom that D0, which links newlib'sfree/mallocinto firmware that never allocates; - under multiple inheritance, emit one non-virtual thunk per base, and at
-O2GCC inlines the whole destructor body into each thunk.
Measured (e-foc, EK-TM4C123GXL, sync_foc_sensored, RelWithDebInfo, GCC 13.2)
- e-foc applied the same rule to its own ~40 interfaces. Switching them to protected non-virtual destructors cut 18.4 KB of flash (231,936 → 213,496 B): 13.3 KB of destructor thunks and 3.2 KB of deleting destructors went away.
- The toolbox's own share in that firmware is small today: 96 B in 12 destructor functions (
Lqr<float,2,1,300>,Lqr<float,3,1,300>,DeadbeatControl<float,1,1,1>/<…,1,1,2>,PidIncrementalSynchronous<float>,RecursiveLeastSquares<float,3>). But those are 6 of the 18 remaining callers ofoperator deletethat keepmalloc/freelinked. The cost also grows with every toolbox class a user adopts, and much faster as soon as one is multiply inherited.
Proposal
Use the EMIL convention: a protected, non-virtual destructor on interfaces, and no destructor declaration on concrete classes:
class StateFeedbackController
{
protected:
~StateFeedbackController() = default;
public:
virtual ... = 0;
};This also turns accidental deletion through an interface into a compile error. Code that owns objects through a base pointer has to change too, e.g. numerical/analysis/windowing/test/TestWindowing.cpp, which holds std::unique_ptr<windowing::Window<T>>. It can hold the concrete type instead, or that one host-only interface can keep a virtual destructor as a documented exception.
Sites on main (c181cd1):
numerical/optimization/BayesianOptimization.hpp:55(BlackBoxObjective)numerical/optimization/Optimizer.hpp:15numerical/optimization/ObjectiveFunction.hpp:18numerical/optimization/StepOptimizer.hpp:13numerical/controllers/interfaces/PidDriver.hpp:12numerical/controllers/interfaces/PidController.hpp:29(AsynchronousPidController)numerical/controllers/interfaces/OutputFeedbackController.hpp:24numerical/controllers/interfaces/StateFeedbackController.hpp:22numerical/controllers/implementations/Feedforward2Dof.hpp:15(Feedforward),:26(FeedbackLaw)numerical/nonlinear_control/FeedbackLinearization.hpp:21(ControlAffineModel)numerical/nonlinear_control/BacksteppingControl.hpp:21(StrictFeedbackModel)numerical/solvers/OdeSystem.hpp:15,:28numerical/solvers/Solver.hpp:16numerical/regularization/Regularization.hpp:17numerical/estimators/Estimator.hpp:14(OfflineEstimator),:32(OnlineEstimator)numerical/analysis/windowing/Windowing.hpp:17(Window)numerical/analysis/FastFourierTransform.hpp:12(TwiddleFactors),:24
Update the rule in AGENTS.md, CLAUDE.md and .github/copilot-instructions.md along with the code.
Activity