constexpr CRTP模式省略虚析构函数的正确性及替代方案咨询
问题解答
1. 注释掉虚析构交由编译器自动生成的正确性与内存泄漏风险
你当前注释掉虚析构的做法仅在不会通过基类指针删除派生类对象的前提下是正确的,不会引发内存泄漏。
虚析构的唯一作用是保证「通过基类指针指向派生类对象、再删除基类指针」场景下派生类析构函数能被正确调用。而CRTP是静态多态实现,本身设计目的就不是用于运行时多态的基类指针持有场景,只要你代码中不会出现上述误用场景,就不会有问题。
但这个方案存在安全隐患:如果其他开发者误用基类指针持有派生类对象并执行删除操作,会触发未定义行为,可能出现内存泄漏、资源泄漏等问题。
2. 将析构函数设置为protected是更优方案
这是目前适配constexpr场景的最优解,理由如下:
- 从语法层面禁止了外部删除基类指针的操作,彻底杜绝上述误用隐患,即使有人写错代码也会直接编译报错
- protected非虚析构完全支持
constexpr标记,不会和你的编译期需求冲突 - 不管基类是否包含数据成员、派生类是否包含动态申请的资源,该方案都适用,通用性极强
- 不影响派生类的正常析构:派生类继承后可以正常声明public析构,销毁派生类对象时能正常调用整个继承链的析构函数,不会出现资源泄漏
3. 适配constexpr场景的代码修改示例
你只需要给所有CRTP基类新增protected的constexpr析构即可,修改后核心代码如下:
namespace lib { template <typename Derived, template<typename> class CrtpType> struct Crtp { [[nodiscard]] Derived& child() noexcept { return static_cast<Derived&>(*this); } [[nodiscard]] constexpr Derived const& child() const noexcept { return static_cast<const Derived&>(*this); } private: constexpr Crtp() = default; friend CrtpType<Derived>; protected: constexpr ~Crtp() = default; }; template<typename Derived> struct FeatureNamesInterface : Crtp<Derived, FeatureNamesInterface> { constexpr FeatureNamesInterface() = default; [[nodiscard]] constexpr auto& GetFeatureNames() const noexcept { return Crtp<Derived, FeatureNamesInterface>::child().GetNames(); } protected: constexpr ~FeatureNamesInterface() = default; }; template<typename Derived> struct FeatureDataInterface : Crtp<Derived, FeatureDataInterface> { constexpr FeatureDataInterface() = default; [[nodiscard]] constexpr auto GetFeatureData() const { return Crtp<Derived, FeatureDataInterface>::child()(); } protected: constexpr ~FeatureDataInterface() = default; }; }
该方案完全兼容你现有的派生类实现,不管是编译期constexpr调用还是运行时调用都可以正常工作,也不存在任何内存泄漏风险。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

