纯虚析构函数 vs. 受保护构造函数 + 虚析构函数

3
当组织继承结构并需要使基类抽象时,是否有理由优先选择纯虚析构函数而不是受保护的构造函数(或反之亦然)?这个问题已经在这里存在,但被接受的答案并没有真正回答它。
Base *ptr = new Derived();
delete ptr;

这两种方法都可以解决问题:

  • 将析构函数设为纯虚函数并定义其内容
  • 将析构函数设为虚函数并将构造函数设为保护的

据我所知,唯一不同的是,在尝试Base *ptr = new Base()时编译器报错的情况:
在第一种情况下是Cannot instantiate abstract class,而在第二种情况下是Cannot access protected member,前者更为准确。这是一种风格问题,还是我有不了解的地方?

另外一个相关的问题是,是否会对继承链中不严格属于基类但仍应该是抽象的某个类应用相同的规则(例如,MonsterGameObject继承而来,不能被创建,但从Monster继承而来的Orc则可以)?

2个回答

6

如果只有默认构造函数是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,包括任何隐式定义的构造函数。这似乎比仅创建纯虚析构函数并实现它更繁琐。而且很容易因疏忽而漏掉某些内容。

从更概念层面上来说,如果您想使一个类成为抽象类,那么请将其设为抽象类。实现一些看起来像是抽象类但只有在不长时间盯着看时才有用,我认为并不是很有帮助。


更抽象地说,如果你想让一个类成为抽象类,那就将它声明为抽象类。这是个好建议 - 我想我理解上的问题在于“无法创建的基类”并不等同于“抽象基类”。再次感谢您提醒我隐式构造函数的存在。 - Artalus

1
如果您有一个非抽象类,其构造函数是受保护的,则仍然可以在派生类的方法中不希望实例化该类,因为它将访问基类的受保护成员:
// Devious code to bypass protection
class Devious : public ProtectedBase {
public:
    ProtectedBase *createBase() const {
        return new ProtectedBase();
    }
}

通过将类设为抽象类,您可以防止这种情况发生。要使类成为抽象类,您不需要使析构函数成为纯虚函数,只要至少有一个成员函数是纯虚函数即可。如果没有其他合适的成员函数可以设置为纯虚函数,那么将析构函数设置为纯虚函数只是一种惯例。
针对您的第二个问题,基类在继承层次结构的顶部还是从其他基类继承并不重要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接