3

考虑以下代码:

#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如果我想要from的公共继承Base,并且如果我不想在基类中使用虚拟析构函数,那么构造函数 ( MARKER 1) 和析构函数 ( MARKER 2)的最佳关键字是什么,Base以保证不会发生任何坏事?

4

3 回答 3

3

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

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

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

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

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

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

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

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

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

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

于 2013-01-10T11:32:28.510 回答
1

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

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

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

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

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

  • 定义一个受保护的构造函数。
  • 不要定义析构函数——或者,如果 CRTP 语义需要,将其定义为public(和非虚拟的)。
于 2013-01-10T13:10:07.177 回答
0

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

于 2013-01-10T11:18:22.250 回答