我创建了奇怪地反复出现的模板模式的constexpr版本,除了“在正常情况下”应该标记为virtual的析构函数之外,所有这些都像预期的那样工作。据我所知,virtual是constexpr的重要敌人。
在我的示例中,我实现了两个没有数据成员的接口。在一般情况下(有数据成员),让virtual ~Crtp() = default;、virtual ~FeatureNamesInterface() = default;和virtual ~FeatureValuesInterface() = default;注释掉并让编译器定义析构函数是正确的吗?这种方法有内存泄漏吗?这是一个更好的方法来保护他们吗?任何其他解决方案,与同事合作,将受到欢迎!
接口代码如下所示
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;
}
};
}完整的示例可以找到这里。
发布于 2021-10-25 18:39:52
除“在正常情况下”应标记为虚拟的析构函数外
这里有些东西是不确定的。听起来您的操作假设是“所有类都应该有一个虚拟析构函数”,这是不正确的。
只有在可能从指向基的指针中删除派生类时,才需要virtual析构函数。出于安全考虑,如果基础有任何其他虚拟方法,则通常还鼓励系统地有一个虚拟析构函数,因为由此产生的额外开销可以忽略不计,因为已经存在一个虚拟析构函数。
另一方面,如果类没有虚拟方法,那么通常没有理由在指向基的指针中持有指向派生对象的指针。最重要的是,虚拟析构函数的开销按比例增加了很多,因为没有虚拟析构器就根本不需要vtable。除非您确信您需要虚拟析构函数,否则它可能会造成更多的伤害而不是好处。
在CRTP的情况下,这就更加明确了,因为指向基本CRTP类型的指针通常从一开始就不是一件事,因为:
这是一个更好的方法来保护他们吗?
对于要继承的非多态类来说,这通常是一种很好的方法。它确保唯一调用析构函数的是派生类的析构函数,这提供了一个硬的保证,即销毁永远不需要是虚拟的。
然而,就CRTP而言,它几乎接近于过度杀戮。只需将析构函数排除在外通常被视为可以接受。
https://stackoverflow.com/questions/69712962
复制相似问题