纯虚拟析构函数与受保护的构造函数 + 虚拟 dtor
Pure virtual destructor vs. protected constructor + virtual dtor
当组织继承结构并需要使基 class 抽象时,是否有任何理由更喜欢纯虚拟析构函数而不是受保护的构造函数(反之亦然)?这个问题已经存在 here,但接受的答案并没有真正回答它。
的问题
Base *ptr = new Derived();
delete ptr;
两种方法都可以解决:
- 使dtor成为纯虚函数并为其定义body
- 使 dtor 虚拟化并使 ctor 受保护
据我了解,唯一不同的是编译器在尝试 Base *ptr = new Base()
时出现错误:第一种情况下的 Cannot instantiate abstract class
与第二种情况下的 Cannot access protected member
相比,第一种情况是更准确的。这是风格的问题,还是有什么我没意识到的?
作为一个有点相关的问题,相同的规则是否适用于继承链中的某些 class 严格来说不是基础的,但仍然应该是抽象的(即 Monster
继承自 GameObject
无法创建,但是 Orc
继承自 Monster
可以)?
如果只有默认构造函数是protected
,您仍然可以使用隐式定义的复制构造函数创建实例:
struct ProtectedBase
{
virtual ~ProtectedBase() = default;
protected:
ProtectedBase(){}
};
struct ProtectedDerived : ProtectedBase {};
struct VirtualBase {
virtual ~VirtualBase() = 0;
};
VirtualBase::~VirtualBase() = default;
struct VirtualDerived : VirtualBase {};
int main() {
ProtectedBase a { ProtectedDerived{} }; //valid
VirtualBase b { VirtualDerived{} }; //invalid
}
因此您需要将所有构造函数显式标记为 protected
,包括任何隐式定义的构造函数。这似乎比简单地制作一个纯虚拟的析构函数并实现它需要更多的工作。也更容易错过一些东西。
在更概念化的层面上,如果你想做一个 class 抽象,那就让它抽象。实施使它看起来像抽象的东西 class 只要您不盯着看太久就不是很有帮助恕我直言。
如果您有一个带有受保护构造函数的非抽象 class,您仍然可以在派生 class 的方法中意外地实例化 class,因为它可以访问致基地 class:
的受保护成员
// Devious code to bypass protection
class Devious : public ProtectedBase {
public:
ProtectedBase *createBase() const {
return new ProtectedBase();
}
}
通过使 class 抽象化,您可以防止这种可能性。要使 class 抽象,不需要将析构函数设为纯虚函数,只要至少有一个成员函数是纯虚函数即可。如果没有其他成员函数可以合理地设置为纯虚拟,则将析构函数设置为纯虚拟只是一种约定。
在回答你的第二个问题时,基础是在层次结构的顶部还是从其他基础继承并不重要。
当组织继承结构并需要使基 class 抽象时,是否有任何理由更喜欢纯虚拟析构函数而不是受保护的构造函数(反之亦然)?这个问题已经存在 here,但接受的答案并没有真正回答它。
的问题Base *ptr = new Derived();
delete ptr;
两种方法都可以解决:
- 使dtor成为纯虚函数并为其定义body
- 使 dtor 虚拟化并使 ctor 受保护
据我了解,唯一不同的是编译器在尝试 Base *ptr = new Base()
时出现错误:第一种情况下的 Cannot instantiate abstract class
与第二种情况下的 Cannot access protected member
相比,第一种情况是更准确的。这是风格的问题,还是有什么我没意识到的?
作为一个有点相关的问题,相同的规则是否适用于继承链中的某些 class 严格来说不是基础的,但仍然应该是抽象的(即 Monster
继承自 GameObject
无法创建,但是 Orc
继承自 Monster
可以)?
如果只有默认构造函数是protected
,您仍然可以使用隐式定义的复制构造函数创建实例:
struct ProtectedBase
{
virtual ~ProtectedBase() = default;
protected:
ProtectedBase(){}
};
struct ProtectedDerived : ProtectedBase {};
struct VirtualBase {
virtual ~VirtualBase() = 0;
};
VirtualBase::~VirtualBase() = default;
struct VirtualDerived : VirtualBase {};
int main() {
ProtectedBase a { ProtectedDerived{} }; //valid
VirtualBase b { VirtualDerived{} }; //invalid
}
因此您需要将所有构造函数显式标记为 protected
,包括任何隐式定义的构造函数。这似乎比简单地制作一个纯虚拟的析构函数并实现它需要更多的工作。也更容易错过一些东西。
在更概念化的层面上,如果你想做一个 class 抽象,那就让它抽象。实施使它看起来像抽象的东西 class 只要您不盯着看太久就不是很有帮助恕我直言。
如果您有一个带有受保护构造函数的非抽象 class,您仍然可以在派生 class 的方法中意外地实例化 class,因为它可以访问致基地 class:
的受保护成员// Devious code to bypass protection
class Devious : public ProtectedBase {
public:
ProtectedBase *createBase() const {
return new ProtectedBase();
}
}
通过使 class 抽象化,您可以防止这种可能性。要使 class 抽象,不需要将析构函数设为纯虚函数,只要至少有一个成员函数是纯虚函数即可。如果没有其他成员函数可以合理地设置为纯虚拟,则将析构函数设置为纯虚拟只是一种约定。
在回答你的第二个问题时,基础是在层次结构的顶部还是从其他基础继承并不重要。