首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Constexpr CRTP破坏者

Constexpr CRTP破坏者
EN

Stack Overflow用户
提问于 2021-10-25 18:20:28
回答 1查看 105关注 0票数 3

我创建了奇怪地反复出现的模板模式的constexpr版本,除了“在正常情况下”应该标记为virtual的析构函数之外,所有这些都像预期的那样工作。据我所知,virtualconstexpr的重要敌人。

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

接口代码如下所示

代码语言:javascript
复制
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()(); }
    };
}

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

代码语言:javascript
复制
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;
        }
    };
}

完整的示例可以找到这里

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-10-25 18:39:52

除“在正常情况下”应标记为虚拟的析构函数外

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

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

另一方面,如果类没有虚拟方法,那么通常没有理由在指向基的指针中持有指向派生对象的指针。最重要的是,虚拟析构函数的开销按比例增加了很多,因为没有虚拟析构器就根本不需要vtable。除非您确信您需要虚拟析构函数,否则它可能会造成更多的伤害而不是好处。

在CRTP的情况下,这就更加明确了,因为指向基本CRTP类型的指针通常从一开始就不是一件事,因为:

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

这是一个更好的方法来保护他们吗?

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

然而,就CRTP而言,它几乎接近于过度杀戮。只需将析构函数排除在外通常被视为可以接受。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69712962

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档