没有纯虚函数的C++抽象类?

32

我有一个基类

class ShapeF
{
public:
    ShapeF();
    virtual ~ShapeF();

    inline void SetPosition(const Vector2& inPosition) { mPosition.Set(inPosition); }

protected:
    Vector2 mPosition;
}

显然,有一些省略的代码,但你明白我的意思。 我使用这个作为模板,并且使用一些有趣(省略)的枚举类型来确定我正在使用的形状。

class RotatedRectangleF : public ShapeF
{
public:
    RotatedRectangleF();
    virtual ~RotatedRectangleF();
protected:
    float mWidth;
    float mHeight;
    float mRotation;
}

ShapeF通过定位和定义类型的枚举来完成其工作。它具有访问器和修改器,但没有方法。

我能否将ShapeF设置为抽象类,以确保没有人试图实例化ShapeF类型的对象?

通常,可以通过在ShapeF中拥有纯虚函数来实现这一点。

//ShapeF.h
virtual void Collides(const ShapeF& inShape) = 0;

然而,我目前正在处理一个单独的类中的碰撞问题。 我可以将所有内容移动过去,但我想知道是否有一种方法可以使一个类成为抽象类,而不需要使用纯虚函数。


你正在尝试使用标记(枚举)和强制类型转换重新实现动态分派... 这很快将成为未来的痛苦。我建议重新考虑设计。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas 我觉得这是最简单的方法,将所有内容进行静态转换。如果我想重新设计的话,我会完全删除 ShapeF(),通过将每个形状作为其自己的类来实现多态性。除非你有不同的建议? - MintyAnt
3个回答

56

你可以声明并实现一个纯虚析构函数:

class ShapeF
{
public:
    virtual ~ShapeF() = 0;
    ...
};

ShapeF::~ShapeF() {}

这只是一个小小的改动,不会影响您已有的内容,并且将阻止直接实例化ShapeF。派生类不需要做出任何改变。


6
快速问题 - 当~ShapeF被声明为“纯虚函数”时,为什么需要实现它? - Matt Kline
谢谢,这个完美地运行了。你有什么想法为什么它能够工作吗?我以为一个纯虚函数是不能被定义的。 - MintyAnt
9
必须对其进行定义,否则派生类无法销毁其基类。任何纯虚函数都可以被定义,这允许派生类委托给它们。 - Fred Larson
如果我声明一个虚析构函数,然后为它定义一个函数体,那么我是否需要在每个派生类的析构函数中显式调用基类的析构函数?这将需要大量的无用代码:我必须在每个派生类中定义空析构函数,只是为了调用基类的析构函数。 - Serge Rogatch
请注意,这种解决方案会产生存储开销-由于(未使用的)vtable指针,ShapeF的大小将至少增加一个指针大小。下面的受保护构造函数解决方案不会遇到这个问题。 - offlinemark
1
这是一种过于严谨的解决方案,因为 std::is_abstract_v<ShapeF> 也同意了这种方法。而使用受保护的构造函数则无法提供此功能。 - Thomas Eding

28

尝试使用受保护的构造函数


@DavidRodríguez-dribeas 为什么析构函数应该是受保护的? 如果它受保护,就无法删除对象。 - James Kanze
1
@JamesKanze:我猜“应该”这个词有点过于强烈了。我觉得析构函数只是被声明为virtual的唯一原因是为了遵循一个建议,即如果类型是可派生的,则始终将析构函数声明为虚函数。将析构函数声明为protected可以解决同样的潜在问题,并提供了解决不实例化的问题的方法(至少在堆栈中,如果你对内存泄漏满意的话,在堆中也是如此)。无论如何,我认为整个设计都应该重新审视一下(正如我在评论中提到的)。 - David Rodríguez - dribeas
1
我喜欢Herb Sutter有关使基类析构函数成为protected和non-virtual或public和virtual的建议。请参见Virtuality,第4条指南。区别在于您是否想允许多态删除。 - Fred Larson
@DavidRodríguez-dribeas 我猜如果这个类有虚函数,而他想让它成为抽象的,目标就是通过指向它的指针完成所有操作,而不是通过派生类。毕竟,这是最常见的情况。如果它的析构函数是受保护的,那意味着您将无法删除它。 - James Kanze
@DavidRodríguez-dribeas 确实设计看起来有些可疑,所以很难确切知道他想做什么。 - James Kanze
显示剩余2条评论

-2
如果您的编译器是Visual C++,那么也有一个“abstract”关键字:
class MyClass abstract
{
    // whatever...
};

虽然据我所知它在其他编译器上无法编译,但它是微软的自定义关键字之一。


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