接口->抽象->具体类设计中的虚拟解构器

发布于 2024-12-09 16:42:36 字数 4216 浏览 0 评论 0原文

我自己尝试通过在 StackOverflow 上查找几个问题来回答这个问题。尽管我认为我理解正确,但我无法解决这个问题。这给我留下了唯一明显的观察结果:我仍然不明白。

我在这篇文章的底部对问题进行了总结,其中的所有内容都是我收集的信息和该问题的背景。

所以,我知道当你有一个基类和一个派生类时,你的解构函数应该在基类中标记为虚拟的。允许多态性。

但是,我似乎无法编译我的代码,或者当它编译时,它不会由于“未定义的引用”而链接。我一直在来来回回地改变,但我似乎始终没有摆脱这个循环。

基本上我有一个接口,定义如下:

#ifndef GUIELEMENT_H_
#define GUIELEMENT_H_

class GuiElement {
    public:
    virtual ~GuiElement();
    virtual void draw() = 0;
};

#endif /* GUIELEMENT_H_ */

我有几个由此扩展的对象。一个简单的关系是 GuiWindow (直接派生自 GuiElement):

#ifndef CGUIWINDOW_H_
#define CGUIWINDOW_H_

#include <assert.h>
#include <cstddef>

#include "../GuiElement.h"
#include "../GuiInteractionDelegate.h"

class GuiWindow : public GuiElement {

    public:
        GuiWindow(GuiInteractionDelegate * guiInteractionDelegate) {
            assert(guiInteractionDelegate);
            interactionDelegate = guiInteractionDelegate;
        }

        ~GuiWindow() {
            //delete interactionDelegate;
        }

        // called each frame, delegates its behavior to the given concrete cGuiWindowDelegate class.
        void interact() {
            interactionDelegate->interact(this);
        }

    private:
        GuiInteractionDelegate * interactionDelegate;

};

#endif /* CGUIWINDOW_H_ */

此代码不链接,给我:

对“GuiElement::~GuiElement()”的未定义引用

我认为在 GuiWindow 类中实现就足够了?这是正确的吗?

接下来真正困扰我的是,我还有一个从 GuiElement 派生的抽象类,以及其上的具体实现。基本上给予: GuiElement->GuiShape->GuiButton

这是 GuiShape 的标题:

#ifndef GUISHAPE_H_
#define GUISHAPE_H_

#include "../GuiElement.h"
#include "../../gameobjects/Rectangle.h"

class GuiShape : public GuiElement {

    public:
        GuiShape(Rectangle * rect);
        GuiShape(int x, int y, int width, int height);

        ~GuiShape();

        void draw();

        void setX(int value) { rectangle->setStartX(value);     }
        void setY(int value) { rectangle->setStartY(value);     }

        Rectangle * getRectangle() { return rectangle; }

        bool isMouseOverShape();

        void setColors(int darkBorder, int lightBorder, int inner);

        int getDarkBorderColor() { return darkBorderColor; }
        int getLightBorderColor() { return lightBorderColor; }
        int getInnerColor() { return innerColor; }

    protected:
        Rectangle * rectangle;

    private:
        bool rectangleOwner;
        int darkBorderColor;
        int lightBorderColor;
        int innerColor;
};

最后是 GuiButton:

#ifndef CGUIBUTTON_H_
#define CGUIBUTTON_H_

#include <sstream>
#include <string>

#include "allegro.h"

#include "../../gameobjects/Rectangle.h"
#include "GuiShape.h"

class GuiButton : public GuiShape {

    public:
        GuiButton(Rectangle * rect, std::string theLabel);
        GuiButton(int x, int y, int width, int height, std::string theLabel);
        ~GuiButton();

        void draw();

        std::string * getLabel() {
            return label;
        }

        BITMAP * getBitmap() { return bitmap; }
        void setBitmap(BITMAP * value) { bitmap = value; }
        void setHasBorders(bool value) { hasBorders = value; }
        void setPressed(bool value) { pressed = value; }

        bool shouldDrawPressedWhenMouseHovers() { return drawPressedWhenMouseHovers; }
        bool shouldDrawBorders() { return hasBorders; }
        void setDrawPressedWhenMouseHovers(bool value) { drawPressedWhenMouseHovers = value; }
        bool isPressed() { return pressed; }

    private:
        std::string * label;
        bool drawPressedWhenMouseHovers;
        bool hasBorders;
        bool pressed;
        BITMAP * bitmap;

        void drawBackground();
        void drawLighterBorder();
        void drawDarkerBorder();
        void drawButtonUnpressed();
        void drawButtonPressed();
};

#endif /* CGUIBUTTON_H_ */

这使我想到以下问题:

  • 使用从 A->B-> 派生对象的虚拟解构函数的最佳方法是什么? C?
  • C 应该只是具体的虚拟吗?如果是这样,如何释放仅在 B 中定义和处理的资源? (A=GuiElement, B=GuiShape, C=GuiButton)
  • 为什么我会通过 A->B 的直接实现得到“未定义的引用”? (GuiElement->GuiWindow)

预先感谢您的帮助!

I have tried to answer this myself, by looking up several questions at StackOverflow. And although I think I understand this correctly, I can't fix this. Which, leaves me with the only obvious observation: I still don't get it.

I have made a summary of the questions at the bottom of this post, everything in between is information I have gathered and context for this question.

So, I get it that when you have a base class, and a derived class, your deconstructor should be marked virtual in the base class. To allow polymorphism.

But, I cannot seem to get my code to compile, or when it does compile, it does not link due 'undefined references'. I have been changing back and forth, but I never seem to get out of this cycle.

Basically I have an interace, defined like this:

#ifndef GUIELEMENT_H_
#define GUIELEMENT_H_

class GuiElement {
    public:
    virtual ~GuiElement();
    virtual void draw() = 0;
};

#endif /* GUIELEMENT_H_ */

I have several objects extending from this. A simple relation is GuiWindow (directly derives from GuiElement):

#ifndef CGUIWINDOW_H_
#define CGUIWINDOW_H_

#include <assert.h>
#include <cstddef>

#include "../GuiElement.h"
#include "../GuiInteractionDelegate.h"

class GuiWindow : public GuiElement {

    public:
        GuiWindow(GuiInteractionDelegate * guiInteractionDelegate) {
            assert(guiInteractionDelegate);
            interactionDelegate = guiInteractionDelegate;
        }

        ~GuiWindow() {
            //delete interactionDelegate;
        }

        // called each frame, delegates its behavior to the given concrete cGuiWindowDelegate class.
        void interact() {
            interactionDelegate->interact(this);
        }

    private:
        GuiInteractionDelegate * interactionDelegate;

};

#endif /* CGUIWINDOW_H_ */

This code does not link, gives me:

undefined reference to `GuiElement::~GuiElement()'

I thought it was sufficient to have an implementation in the GuiWindow class? Is that correct?

The next thing, which is really bugging me, is that I also have an abstract class derived from GuiElement, and concrete implementations on top of that. Basically giving:
GuiElement->GuiShape->GuiButton

Here is the header of GuiShape:

#ifndef GUISHAPE_H_
#define GUISHAPE_H_

#include "../GuiElement.h"
#include "../../gameobjects/Rectangle.h"

class GuiShape : public GuiElement {

    public:
        GuiShape(Rectangle * rect);
        GuiShape(int x, int y, int width, int height);

        ~GuiShape();

        void draw();

        void setX(int value) { rectangle->setStartX(value);     }
        void setY(int value) { rectangle->setStartY(value);     }

        Rectangle * getRectangle() { return rectangle; }

        bool isMouseOverShape();

        void setColors(int darkBorder, int lightBorder, int inner);

        int getDarkBorderColor() { return darkBorderColor; }
        int getLightBorderColor() { return lightBorderColor; }
        int getInnerColor() { return innerColor; }

    protected:
        Rectangle * rectangle;

    private:
        bool rectangleOwner;
        int darkBorderColor;
        int lightBorderColor;
        int innerColor;
};

And finally GuiButton:

#ifndef CGUIBUTTON_H_
#define CGUIBUTTON_H_

#include <sstream>
#include <string>

#include "allegro.h"

#include "../../gameobjects/Rectangle.h"
#include "GuiShape.h"

class GuiButton : public GuiShape {

    public:
        GuiButton(Rectangle * rect, std::string theLabel);
        GuiButton(int x, int y, int width, int height, std::string theLabel);
        ~GuiButton();

        void draw();

        std::string * getLabel() {
            return label;
        }

        BITMAP * getBitmap() { return bitmap; }
        void setBitmap(BITMAP * value) { bitmap = value; }
        void setHasBorders(bool value) { hasBorders = value; }
        void setPressed(bool value) { pressed = value; }

        bool shouldDrawPressedWhenMouseHovers() { return drawPressedWhenMouseHovers; }
        bool shouldDrawBorders() { return hasBorders; }
        void setDrawPressedWhenMouseHovers(bool value) { drawPressedWhenMouseHovers = value; }
        bool isPressed() { return pressed; }

    private:
        std::string * label;
        bool drawPressedWhenMouseHovers;
        bool hasBorders;
        bool pressed;
        BITMAP * bitmap;

        void drawBackground();
        void drawLighterBorder();
        void drawDarkerBorder();
        void drawButtonUnpressed();
        void drawButtonPressed();
};

#endif /* CGUIBUTTON_H_ */

Which leads me to the following questions:

  • What is the best way to use virtual deconstructors where objects are derived from A->B->C ?
  • Should C only be the concrete virtual? And if so, how do you release resources defined and handled only in B? (A=GuiElement, B=GuiShape, C=GuiButton)
  • Why would I get 'undefined references' with the straight-forward implementation of A->B ? (GuiElement->GuiWindow)

Thanks in advance for your help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

樱娆 2024-12-16 16:42:36

使用从 A->B->C 派生对象的虚拟解构函数的最佳方法是什么?

将基础的(或所有)析构函数标记为虚拟。

C 只能是具体的虚拟吗?如果是这样,如何释放仅在 B 中定义和处理的资源? (A=GuiElement、B=GuiShape、C=GuiButton)

不确定“具体虚拟”是什么意思,但是包含需要销毁的成员的类应该在自己的析构函数中销毁它们。没有例外。当~C被调用时,它会销毁它自己的东西,然后~B将被自动调用。虚拟只是绝对确保首先调用 ~C

为什么直接实现 A->B 会得到“未定义的引用”? (GuiElement->GuiWindow)

virtual ~GuiElement(); 告诉编译器该类有一个稍后定义的析构函数。您想要:

// There is no definition, cannot make a local "GuiElement" variable
// They can only make local "GuiButton" or other derived.
// You can still have pointers to a GuiElement.
// This is called "pure virtual"
virtual ~GuiElement() = 0; 

或:

// There is a definition, someone can make a local "GuiElement" variable
virtual ~GuiElement()  {};

What is the best way to use virtual deconstructors where objects are derived from A->B->C ?

mark the base's (or all) destructor as virtual.

Should C only be the concrete virtual? And if so, how do you release resources defined and handled only in B? (A=GuiElement, B=GuiShape, C=GuiButton)

Not sure what you mean by "concrete virtual" but a class with members that need destroying should destroy them in it's own destructor. No exceptions. when ~C is called, it destroys it's own stuff, and then ~B will be called automatically. The virtual just makes absolutely sure that ~C is called first.

Why would I get 'undefined references' with the straight-forward implementation of A->B ? (GuiElement->GuiWindow)

virtual ~GuiElement(); tells the compiler that the class has a destructor that will be defined later. You wanted either:

// There is no definition, cannot make a local "GuiElement" variable
// They can only make local "GuiButton" or other derived.
// You can still have pointers to a GuiElement.
// This is called "pure virtual"
virtual ~GuiElement() = 0; 

or:

// There is a definition, someone can make a local "GuiElement" variable
virtual ~GuiElement()  {};
記柔刀 2024-12-16 16:42:36

我认为在 GuiWindow 类中实现就足够了?这是正确的吗?

不可以。如果在类中声明了虚函数(不是纯虚函数,如 GuiElement 的析构函数),则必须对其进行定义。

析构函数更进一步:它们必须始终被实现,即使它是纯虚拟的[1]。如果您没有声明它,编译器将为您创建一个(隐式非虚拟的,但如果它将覆盖虚拟析构函数,则它将是虚拟的)。在 C++11 中,您可以将其标记为“默认”(这意味着“编译器,为我实现它”)和“已删除”,这意味着“程序永远不会隐式或显式地破坏这种类型的对象”。

  1. 使用从 A->B- 派生对象的虚拟解构函数的最佳方式是什么
    C?

您通常希望将最顶层的析构函数设为虚拟,这意味着层次结构中的所有析构函数都是虚拟的。

如果是这样,如何释放仅在 B 中定义和处理的资源? (A=GuiElement、B=GuiShape、C=GuiButton)

自然是在 ~B() 中。

[1]:

12.4/7:析构函数可以声明为 virtual (10.3) 或 pure virtual (10.4);如果该类的任何对象或任何
程序中创建派生类时,应定义析构函数。如果一个类有一个带有
虚拟析构函数,它的析构函数(无论是用户声明的还是隐式声明的)都是虚拟的。

I thought it was sufficient to have an implementation in the GuiWindow class? Is that correct?

No. A virtual function (that is not pure virtual, as your destructor of GuiElement) must be defined if it is declared in the class.

Destructors go even further: they must be implemented, always, even if it is pure virtual[1]. If you hadn't declared it, the compiler would create one (implicitly nonvirtual, but would be virtual if it would override a virtual destructor) for you. In C++11, you can just mark it "defaulted" (which means "compiler, implement that for me") and "deleted" which means "the program may never, implicitly or explicitly, destruct objects of this type".

  1. What is the best way to use virtual deconstructors where objects are derived from A->B-
    C ?

You usually want to make the topmost base's destructor virtual, that means all destructors in the hierarchy are virtual.

And if so, how do you release resources defined and handled only in B? (A=GuiElement, B=GuiShape, C=GuiButton)

In ~B(), naturally.

[1] :

12.4/7: A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any
derived class are created in the program, the destructor shall be defined. If a class has a base class with a
virtual destructor, its destructor (whether user- or implicitly- declared) is virtual.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文