【问题标题】:Dealing with protected/private constructor/destructor for a CRTP design?处理 CRTP 设计的受保护/私有构造函数/析构函数?
【发布时间】:2012-12-24 17:44:29
【问题描述】:

考虑以下代码:

#include <iostream>
#include <type_traits>

// Abstract base class
template<class Crtp>
class Base
{
    // Lifecycle
    public: // MARKER 1
        Base(const int x) :  _x(x) {}
    protected: // MARKER 2
        ~Base() {}

    // Functions
    public:
        int get() {return _x;}
        Crtp& set(const int x) {_x = x; return static_cast<Crtp&>(*this);}

    // Data members
    protected:
        int _x;
};

// Derived class
class Derived
: public Base<Derived>
{
    // Lifecycle
    public:
        Derived(const int x) : Base<Derived>(x) {}
        ~Derived() {}
};

// Main
int main()
{
    Derived d(5);
    std::cout<<d.set(42).get()<<std::endl;
    return 0;
}

如果我想要从Base 公开继承Derived,并且不想在基类中使用虚拟析构函数,那么构造函数 (MARKER 1) 和析构函数的最佳关键字是什么(MARKER 2) 的Base 保证不会发生任何不好的事情?

【问题讨论】:

    标签: c++ constructor destructor crtp


    【解决方案1】:

    无论您使用哪种编程风格,您都可能会做坏事:即使您遵循最好的最佳指南实践。这是其背后的物理因素(并且与减少全局熵的可能性有关)

    也就是说,不要将“经典 OOP”(一种方法)与 C++(一种语言)、OOP 继承(一种关系)与 C++ 继承(一种聚合机制)以及 OOP 多态性(一种模型)与 C++ 运行时和静态多态(一种调度机制)。

    虽然名称有时匹配,但 C++ 事物不一定要服务于 OOP 事物。

    使用一些非虚拟方法从基类进行公共继承是正常的。并且析构函数并不特殊:只是不要在 CRTP 基础上调用 delete。

    与经典 OOP 不同,CRTP 基对每个派生都有不同的类型,因此拥有“指向基的指针”是毫无头绪的,因为没有“指向通用类型的指针”。因此,调用“删除 pbase”的风险非常有限。

    “protected-dtor 范例”仅在您使用 C++ 继承编程 OOP 继承时才有效,该继承通过基于指针的多态来管理(和删除)对象。如果您遵循其他范例,则不应以字面方式对待这些规则。

    在您的情况下,受保护的 dtor 只是拒绝您在堆栈上创建 Base&lt;Derived&gt; 并在 Base* 上调用 delete。你永远不会做的事情,因为没有“Dervied”的 Base 没有任何意义存在,并且拥有 Base&lt;Derived&gt;* 没有任何意义,因为你只能拥有 Derived*,因此同时拥有 public ctor 和 dtor 不会造成特别的混乱。

    但是您甚至可以做相反的选择来同时保护 ctor 和 dtor,因为您永远不会单独构造 Base,因为它总是需要知道 Derived 类型。

    由于 CRTP 的特殊构造,所有经典的 OOP 东西都会导致一种“无关紧要的平衡”,因为不再有“危险的用例”。

    您可以使用或不使用它们,但不会发生特别的坏事。如果您按照设计使用对象的方式使用对象,则不会。

    【讨论】:

      【解决方案2】:

      虽然您的代码有效,但我觉得将 destructor 而不是 constructor 标记为 protected 很奇怪。通常我的理由是您想防止程序员意外创建 CRTP 基础对象。当然,这一切都归结为相同,但这几乎不是规范代码。

      您的代码唯一可以防止的是通过基指针意外删除 CRTP 对象 - 即这样的情况:

      Base<Derived>* base = new Derived;
      delete base;
      

      但这是一种高度人为的情况,不会出现在实际代码中,因为 CRTP 根本不应该以这种方式使用。 CRTP base 是一个实现细节,应该对客户端代码完全隐藏。

      所以我应对这种情况的方法是:

      • 定义受保护的构造函数。
      • 不要定义析构函数 - 或者,如果 CRTP 语义需要,将其定义为 public(非虚拟)。

      【讨论】:

        【解决方案3】:

        没问题,因为析构函数是受保护的,这意味着客户端代码不能删除指向Base的指针,所以Base的析构函数是非虚拟的也没有问题。

        【讨论】:

        • 但是析构函数也不需要protected。它不会带来任何好处。
        猜你喜欢
        • 1970-01-01
        • 2011-03-15
        • 2010-12-16
        • 2019-08-24
        • 2012-11-23
        • 2020-03-06
        • 2016-04-07
        • 2021-10-05
        • 2016-01-22
        相关资源
        最近更新 更多