请考虑以下代码:
#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
),以保证不会发生任何不良事件?
答案 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基础是一个实现细节,应该完全隐藏在客户端代码之外。
所以我对这种情况的处方是:
public
(和非虚拟)。答案 2 :(得分:0)
没有问题,因为析构函数受到保护,这意味着客户端代码无法删除指向Base的指针,因此Base的析构函数是非虚拟的没有问题。