【问题标题】:Virtual deconstructors in interface->abstract->concrete class design接口->抽象->具体类设计中的虚拟解构器
【发布时间】:2011-10-14 22:14:52
【问题描述】:

我自己尝试通过在 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)

提前感谢您的帮助!

【问题讨论】:

  • 在这里得到答案后,请执行以下操作:将调试输出放到所有的析构函数/构造函数中,并观察创建/删除对象时调用它们的顺序。然后你就会明白发生了什么。
  • 虽然我想我知道答案我会试试看:)
  • 我正在尝试我的代码的答案,但我最终得到了解构器的“多个实现”(尽管它是虚拟的,我也给它一个实现)。有什么提示吗?与此同时,我继续努力。
  • 我搞定了。我做了什么: - 我制作了所有析构函数,除了最具体的类是虚拟的。 - 我创建了析构函数的实现,因此它们可以释放资源等。 - 我还确保接口类上的所有虚函数,在基类中也提到了虚函数(A->B->C 中的 B)。
  • 你也应该对构造函数做同样的事情——你会看到破坏的顺序是构造的相反顺序,这当然是有道理的:)

标签: c++ inheritance virtual


【解决方案1】:

使用从 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()  {};

【讨论】:

  • 它是析构函数,而不是解构函数。此外,虚拟 ~GuiElement() {};有一个定义,它是 {}
  • 是的,我最后总是输入“deconstructor”:(第二个是复制粘贴错误。
  • 对于“具体的虚拟”,我的意思是在类中定义虚拟,然后实现它。我今晚会试试看我是否能正常工作,谢谢你的回答
【解决方案2】:

我认为在 GuiWindow 类中有一个实现就足够了吗?对吗?

没有。如果在类中声明了虚函数(不是纯虚函数,作为 GuiElement 的析构函数),则必须定义它。

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

  1. 在对象派生自 A->B- 的情况下,使用虚拟解构器的最佳方法是什么? C ?

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

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

~B(),自然而然。

[1]:

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

【讨论】:

  • @MooingDuck jpalecek 所说的是正确的。 (除了我不认为'你可以将它标记为“默认”'对于我称之为'使用默认实现'声明virtual ~T() = 0;的析构函数的准确描述)。
  • @MooingDuck:这不是真的。即使是纯虚析构函数也需要定义,比较ideone.com/SG1U2ideone.com/jhV2H
  • § 12.4/8 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. 我的立场是正确的,析构函数是一个例外。
  • @LucDanton:这可能有点误导性的表达。 “你可以让它默认”,我的意思是ideone.com/ebq2m
  • @jpalecek 对。这使用默认实现,但是当涉及到语言规则时,析构函数不计为默认值,它是用户定义的(IIRC)。这会影响琐碎之类的事情。
猜你喜欢
  • 1970-01-01
  • 2011-10-25
  • 1970-01-01
  • 2016-04-25
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多