处理CRTP设计的受保护/私有构造函数/析构函数?

时间:2013-01-10 10:52:36

标签: c++ constructor destructor crtp

请考虑以下代码:

#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;
}

如果我想从Derived获得Base的公共继承,如果我不想在基类中使用虚拟析构函数,那么构造函数的最佳关键字是什么({{1 }}和MARKER 1的析构函数(MARKER 2),以保证不会发生任何不良事件?

3 个答案:

答案 0 :(得分:3)

无论你使用什么编程风格,你都可以做点坏事:即使你遵循最好的指导练习。这是物质背后的东西(与减少全球熵的不可能性有关)

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

尽管名称有时会匹配,但C ++ - 事情并不一定要为OOP事物服务。

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

与经典OOP不同,CRTP-base对于每个派生类型都有不同的类型,因此具有“指向基础的指针”是无能为力的,因为没有“指向常见类型的指针”。因此,称为“删除pbase”的风险非常有限。

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

在您的情况下,proteced-dtor只是拒绝您在堆栈上创建Base<Derived>并在Base *上调用delete。你永远不会做的事情,因为没有“Dervied”的Base没有任何意义存在,并且Base<Derived>*没有任何意义,因为你只能有一个Derived*,因此有公共ctor和dtor制作没有特别的混乱。

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

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

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

答案 1 :(得分:1)

当您的代码有效时,我发现将析构函数而不是构造函数标记为protected会很奇怪。通常我的理由是你想要阻止程序员意外创建 CRTP基础对象。当然,这一切都归结为相同,但这不是规范的代码。

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

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

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

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

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

答案 2 :(得分:0)

没有问题,因为析构函数受到保护,这意味着客户端代码无法删除指向Base的指针,因此Base的析构函数是非虚拟的没有问题。