COMP: Move inline = default destructors to .cxx for 30 exported classes#6002
Conversation
|
| Filename | Overview |
|---|---|
| Modules/Core/Common/include/itkEquivalencyTable.h | Destructor changed from = default inline to a declaration; definition moved to .cxx to fix ABI visibility. |
| Modules/Core/Common/src/itkEquivalencyTable.cxx | Adds EquivalencyTable::~EquivalencyTable() = default; at the top of the namespace block to anchor destructor thunks with exported visibility. |
| Modules/Numerics/Optimizers/src/itkExhaustiveOptimizer.cxx | Destructor definition added correctly, but leaves a double blank line before the constructor definition (minor style issue). |
| Modules/Numerics/Optimizers/src/itkSPSAOptimizer.cxx | Destructor definition added correctly; introduces a double blank line before the constructor definition (same minor style issue as ExhaustiveOptimizer). |
| Modules/Numerics/Optimizers/src/itkInitializationBiasedParticleSwarmOptimizer.cxx | Destructor definition added correctly; introduces a double blank line before the constructor definition (same minor style issue). |
| Modules/Numerics/FEM/src/itkFEMLightObject.cxx | Correctly adds FEMLightObject::~FEMLightObject() = default; inside namespace itk::fem, consistent with the file's existing namespace style. |
| Modules/Numerics/Statistics/src/itkChiSquareDistribution.cxx | Correctly adds destructor definition inside namespace itk::Statistics; single blank line spacing is consistent. |
| Modules/Video/Core/src/itkTemporalProcessObject.cxx | Correctly adds TemporalProcessObject::~TemporalProcessObject() = default; to anchor destructor thunks. |
| Modules/Segmentation/Watersheds/src/itkOneWayEquivalencyTable.cxx | Correctly moves destructor definition to translation unit; consistent spacing. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Exported class header (.h)\n~ClassName() override = default"] -->|"Before fix\n(-fvisibility-inlines-hidden)"| B["Destructor thunk hidden\n(not exported from DSO)"]
B --> C["dynamic_cast fails\nacross DSO boundary"]
D["Exported class header (.h)\n~ClassName() override (declaration only)"] -->|"After fix"| E["Destructor defined in .cxx\nClassName::~ClassName() = default"]
E --> F["Compiled in one TU\nwith default visibility"]
F --> G["Destructor thunks exported\nalongside vtable & typeinfo"]
G --> H["dynamic_cast works correctly\nacross DSO boundary"]
style C fill:#f88,stroke:#c00
style H fill:#8f8,stroke:#080
Reviews (1): Last reviewed commit: "COMP: Move inline = default destructors ..." | Re-trigger Greptile
| ExhaustiveOptimizer::~ExhaustiveOptimizer() = default; | ||
|
|
||
|
|
||
| ExhaustiveOptimizer::ExhaustiveOptimizer() = default; |
There was a problem hiding this comment.
Fixed in the follow-up commit (STYLE: Fix double blank lines in three Optimizers .cxx files). All three files — itkExhaustiveOptimizer.cxx, itkSPSAOptimizer.cxx, and itkInitializationBiasedParticleSwarmOptimizer.cxx — now have a single blank line between the destructor and constructor definitions.
Non-template exported classes with `~ClassName() override = default;` inline in the header have hidden D1Ev/D0Ev destructor thunks under -fvisibility-inlines-hidden, even when the class vtable and typeinfo remain exported. This breaks dynamic_cast across DSO boundaries in shared library builds. Moving the destructor definition out of line to the .cxx file ensures it is compiled in exactly one translation unit, giving it default (exported) visibility and correctly anchoring all destructor thunks. Affected modules: ITKCommon (3), ITKIOImageBase (3), ITKFEM (1), ITKOptimizers (11), ITKStatistics (8), ITKWatersheds (2), ITKVideoCore (1). See: InsightSoftwareConsortium#6000
9dcf0a1 to
d3f17d4
Compare
It was your helpful commit message that made this possible! Thank you for noticing the issue. I used AI to "Create an issue to encapsulate problem that @N-Dekker thinks exists. Investigate the problem with metrics, fix based on findings." ... and then baby-sat and cajoled the solution. :) |
Summary
This PR fixes an ABI issue in shared library builds (investigated and documented in issue #6000):
Problem: Non-template exported classes with
~ClassName() override = default;inline in the header have their destructor thunks (D1Ev/D0Ev) hidden under-fvisibility-inlines-hidden(an ITK global build flag), even when the class vtable and typeinfo remain exported viaitkOverrideGetNameOfClassMacro. This causesdynamic_castfailures across DSO boundaries in shared library consumers.Fix: Move the destructor definition to the corresponding
.cxxfile asClassName::~ClassName() = default;. This compiles it in one TU with default (exported) visibility, correctly anchoring all destructor thunks.Scope: 30 concrete non-template exported classes across 7 ITK modules (ITKCommon, ITKIOImageBase, ITKFEM, ITKOptimizers, ITKStatistics, ITKWatersheds, ITKVideoCore). Template classes were intentionally excluded — their destructors cannot be defined in
.cxxfiles without explicit template instantiation boilerplate.Verification: Full ITK build passes cleanly; all affected module tests pass.
Affected Classes (30)
EquivalencyTable,LoggerManager,LoggerOutputArchetypeSeriesFileNames,NumericSeriesFileNames,RegularExpressionSeriesFileNamesFEMLightObjectExhaustiveOptimizer,GradientDescentOptimizer,InitializationBiasedParticleSwarmOptimizer,MultipleValuedNonLinearOptimizer,OnePlusOneEvolutionaryOptimizer,Optimizer,QuaternionRigidTransformGradientDescentOptimizer,RegularStepGradientDescentBaseOptimizer,RegularStepGradientDescentOptimizer,SingleValuedNonLinearOptimizer,SPSAOptimizer,VersorTransformOptimizerChiSquareDistribution,DenseFrequencyContainer2,GaussianDistribution,MaximumDecisionRule,MaximumRatioDecisionRule,MinimumDecisionRule,SparseFrequencyContainer2,TDistributionOneWayEquivalencyTable,WatershedMiniPipelineProgressCommandTemporalProcessObjectRelated
LinearSystemWrapperDenseVNL)