c++ - Constexpr CRTP析构函数

标签 c++ destructor compile-time crtp

我创建了 Curiously Recurring Template Pattern 的 constexpr 版本并且一切似乎都按预期工作,除了“在正常情况下”应该被标记为 virtual 的析构函数.据我了解,virtualconstexpr 的死敌。

在我的示例中,我实现了两个没有数据成员的接口(interface)。在一般情况下(使用数据成员)让 virtual ~Crtp() = default;virtual ~FeatureNamesInterface() = default; 是否正确virtual ~FeatureValuesInterface() = default; 注释掉让编译器定义析构函数?这种方法有内存泄漏吗?保护它们是更好的方法吗?欢迎使用 constexpr 的任何其他解决方案!

界面代码是这样的

namespace lib
{
    template <typename Derived, template<typename> class CrtpType>
    struct Crtp
    {
        //virtual ~Crtp() = default;
        [[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>;
    };

    template<typename Derived>
    struct FeatureNamesInterface : Crtp<Derived, FeatureNamesInterface>
    {
        constexpr FeatureNamesInterface() = default;
        //virtual ~FeatureNamesInterface() = default;
        [[nodiscard]] constexpr auto& GetFeatureNames() const noexcept { return Crtp<Derived, FeatureNamesInterface>::child().GetNames(); }
    };

    template<typename Derived>
    struct FeatureDataInterface : Crtp<Derived, FeatureDataInterface>
    {
        constexpr FeatureDataInterface() = default;
        //virtual ~FeatureValuesInterface() = default;
        [[nodiscard]] constexpr auto GetFeatureData() const { return Crtp<Derived, FeatureDataInterface>::child()(); }
    };
}

两个示例类的实现如下所示

namespace impl
{
    class ChildOne final : public lib::FeatureNamesInterface<ChildOne>, public lib::FeatureDataInterface<ChildOne>
    {
        static constexpr std::array mNames{"X"sv, "Y"sv, "Z"sv};
    public:
        constexpr ChildOne() : FeatureNamesInterface(), FeatureDataInterface() {}
        ~ChildOne() = default;
        
        [[nodiscard]] constexpr auto& GetNames() const noexcept { return mNames; }
        
        [[nodiscard]] constexpr auto operator()() const noexcept
        {
            std::array<std::pair<std::string_view, double>, mNames.size()> data;
            double value = 1.0;
            for (std::size_t i = 0; const auto& name : mNames)
                data[i++] = {name, value++};

            return data;
        }
    };

    class ChildTwo final : public lib::FeatureNamesInterface<ChildTwo>, public lib::FeatureDataInterface<ChildTwo>
    {
        static constexpr std::array mNames{"A"sv, "B"sv, "C"sv, "D"sv, "E"sv, "F"sv};
    public:
        constexpr ChildTwo() : FeatureNamesInterface(), FeatureDataInterface() {}
        ~ChildTwo() = default;
        
        [[nodiscard]] constexpr auto& GetNames() const noexcept { return mNames; }

        [[nodiscard]] constexpr auto operator()() const noexcept
        {
            std::array<std::pair<std::string_view, double>, mNames.size()> data;
            double value = 4.0;
            for (std::size_t i = 0; const auto& name : mNames)
                data[i++] = {name, value++};

            return data;
        }
    };
}

可以找到完整的例子here .

最佳答案

except the destructor who "under normal circumstances" should be marked as virtual

这里有些问题。听起来您是在假设“所有类都应该有一个虚拟析构函数”,这是不正确的。

virtual 析构函数仅在派生类有可能从指向基类的指针中删除时才需要。出于安全考虑,如果基类具有任何其他虚拟方法,通常鼓励系统地拥有一个虚拟析构函数,因为由此产生的额外开销可以忽略不计,因为已经存在一个 vtable。 p>

另一方面,如果一个类没有虚方法,那么通常没有理由在指向基类的指针中保存指向派生对象的拥有指针。最重要的是,虚拟析构函数的开销成比例地变得更大,因为如果没有它就根本不需要 vtable。除非您确定需要它,否则虚拟析构函数可能弊大于利。

在 CRTP 的情况下,它甚至更加明确,因为指向基本 CRTP 类型的指针通常从来都不是一回事,因为:

  • 他们只有一个派生类。
  • 由于对派生类的转换,基类本身无法使用。

Is it a better approach to make them protected?

对于旨在被继承的非多态类,这通常是一种很好的方法。它确保唯一调用析构函数的是派生类的析构函数,这为析构永远不需要是虚拟的提供了硬性保证。

不过,就 CRTP 而言,它几乎近乎矫枉过正。仅让析构函数处于默认状态通常被认为是可以接受的。

关于c++ - Constexpr CRTP析构函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69712962/

相关文章:

c++11 - 为什么在编译时不评估 constexpr (MSVC 2015)?

支持 xep-0234(jingle) 的 C++ XMPP 库

c++ - vtkRenderer 错误

c++ - 这个循环的大 O 是什么?

c++ - Qt5.2.0; Debian 喘息 : QSqlDatabase destructor causes segfault

c++ - Elem 没有命名类型?

c++ - 完美转发和 std::forward<T> 的使用

c++ - 堆栈展开和指针

c++ - C++ 析构函数的奇怪行为

python - python中的"Precompile"函数,编译时计算