【问题标题】:Abstract class without abstract methods没有抽象方法的抽象类
【发布时间】:2011-07-10 16:21:47
【问题描述】:

是否可以在不声明任何抽象方法的情况下在 C++ 中创建类抽象?目前,我有一个带有 StaticSprite 和 DynamicSprite 子类的 Sprite 类。我想让 Sprite 类抽象化。

问题是他们没有共享任何方法。好吧,StaticSprite 和 DynamicSprite 可能共享一个 draw() 方法,但是这个方法的参数不同,所以这不是一个选项。

谢谢!

编辑: 这是演示我正在尝试做的代码:

精灵:

class Sprite
{
    public:
        Sprite(HINSTANCE hAppInst, int imageID, int maskID);
        ~Sprite();

    protected:
        HINSTANCE hAppInst;
        HBITMAP hImage;
        HBITMAP hMask;
        BITMAP imageBM;
        BITMAP maskBM;
        HDC hSpriteDC;
};

静态精灵:

class StaticSprite : public Sprite
{
    public:
        StaticSprite(HINSTANCE hAppInst, int imageID, int maskID);
        ~StaticSprite();

        void draw(Position* pos, HDC hBackbufferDC);
};

动态精灵:

class DynamicSprite : public Sprite
{
    public:
        DynamicSprite(HINSTANCE hAppInst, int imageID, int maskID);
        ~DynamicSprite();

        void draw(HDC hBackbufferDC);
};

如您所见,创建一个 Sprite 对象是没有用的,所以我想将该类抽象化。但我不能使 draw() 抽象,因为它使用不同的参数。

【问题讨论】:

  • 为什么要抽象没有抽象方法?
  • 他们可能不希望它被实例化
  • 拥有一个没有方法可继承的基类有什么意义?
  • 因为那个类只有一些字段和一个构造函数/析构函数,但是类的行为应该由子类来实现。我知道这在 Java 中是可能的,所以我想知道 c++ 是否也支持这个。
  • 我已经添加了我的代码来演示我正在尝试做的事情。

标签: c++


【解决方案1】:

你可以将你的析构函数声明为纯虚函数,因为所有类都有一个。

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

但是,您需要在别处定义此析构函数。

AbstractClass::~AbstractClass() {}

【讨论】:

  • 由于基类析构函数是虚拟的,派生类析构函数的析构函数将在需要的时间和地点被调用。应该没有问题。
  • @Bv202 - 您还提供了析构函数的实现,因此您可以在那里进行清理。
  • @Bv202 你仍然可以实现一个纯虚析构函数来清理内存。
  • 好吧,这可能是也可能不是 OP 想要的——它确实增加了类的开销并改变了语义。
  • 如果它包含数据管理,它不是很抽象,是吗?另一种选择是使析构函数受到保护。你确实从中获得了那种力量,因为它不能单独被摧毁。
【解决方案2】:

如果类什么都不做,什么也不提供,只是作为一个标记存在,没有理由担心它是否是抽象的。这基本上不是问题。

如果您绝对希望确保除了在继承上下文之外永远不会实例化,请使用protected constructors

请避免制作纯虚析构函数。疯狂的谎言就是这样。

【讨论】:

    【解决方案3】:

    没有。

    你当然可以用这个:

    class Sprite
    {
    };
    

    当然,当您尝试创建它的实例时,编译器不会抱怨。

    你可以添加一个纯虚析构函数:

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

    或者你可以保护构造函数,停止实例化:

    class Sprite
    {
    protected:
        Sprite ();
    };
    

    【讨论】:

      【解决方案4】:

      针对你帖子的具体情况,考虑私有继承。这样你就可以懒惰地使用基础的实现,而不会向世界宣传虚假的 IS-A 关系。

      【讨论】:

        【解决方案5】:

        传统的做法是制作一个纯虚析构函数,然后实现它。

        // .h
        struct Foo {
          virtual ~Foo() = 0;
        };
        
        // .cpp
        Foo::~Foo() {
        }
        

        【讨论】:

        • 不能实现纯虚拟。
        • @Cthutu 其实你可以实现纯虚方法。这只是意味着孩子必须再次实现它。
        • 在析构函数的情况下,你必须。
        • 是的,你可以,参见例如10.4/2:“纯虚函数仅在使用限定 ID 语法 (5.1) 显式调用时才需要定义。”和 12.4/7:“析构函数可以声明为虚拟 (10.3) 或纯虚拟 (10.4);如果在程序中创建了该类或任何派生类的任何对象,则应定义析构函数。如果类具有基类具有虚拟析构函数的类,其析构函数(无论是用户声明的还是隐式声明的)都是虚拟的。”
        【解决方案6】:

        问题是没有 他们共享的方法。

        嗯,你的问题就在那里。你不应该在这里使用继承!如果您不打算用一个班级代替另一个班级,那么他们就不应该处于父子关系中。

        根据需要,它们应该是完全不相关的、模板或使用组合。

        编辑:根据代码示例,您继承以重用,我建议改用组合。 Sprite 可以是 struct,由 DynamicSpriteStaticSprite 拥有。您可以根据需要将尽可能多/少的辅助逻辑放入Sprite

        【讨论】:

        • 我在第一篇文章中添加了代码。应该怎么解决?
        • -1 因为你不可能知道是否应该使用继承。查看 Alexandrescu 的访问者框架,以找到与您的陈述形成鲜明对比的观点。
        【解决方案7】:

        最好的方法是保护类的构造函数,这样类就不能被直接实例化,因此它本质上是抽象的:

        class SomeBaseClass 
        {
        protected:
            SomeBaseClass() {}
            SomeBaseClass(const SomeBaseClass &) {}
            SomeBaseClass &operator=(const SomeBaseClass&) {}
        public:
            virtual ~SomeBaseClass() {}
            ...
        };
        

        或者,更新的样式:

        class SomeBaseClass 
        {
        protected:
            SomeBaseClass() = default;
            SomeBaseClass(const SomeBaseClass &) = delete;
            SomeBaseClass &operator=(const SomeBaseClass&) = delete;
        public:
            virtual ~SomeBaseClass() = default;
            ...
        };
        

        析构函数通常最好保持公开,因为您可能希望通过其基类指针删除一个对象。

        【讨论】:

          【解决方案8】:

          要创建一个不派生就无法实例化的纯抽象类 - 您必须至少定义一个抽象方法。

          例如。

           virtual void blah() const = 0;
          

          否则你可以使用任何类作为抽象类

          【讨论】:

          • 最终,问题是“为什么要打扰”?
          • @Randolpho - 问题变了。你的意思是为什么要有一个抽象类,或者如果它没有方法为什么要抽象?
          • 是的,问题变了。我讨厌这种情况发生。 :)
          【解决方案9】:

          如果您确实必须使用继承,请考虑使用非虚拟接口 (NVI) 习惯用法(请参阅http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.3

          基本上,基类将定义一组重载的非虚拟方法,这些方法从派生类中调用受保护的纯虚函数。

          你绝对不想做的是重载一个虚函数(看起来你真的很想做)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-03-12
            • 2017-04-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-21
            相关资源
            最近更新 更多