设计:如何避免调用析构函数?

时间:2014-05-14 16:43:06

标签: c++ destructor

我有一个Window类,它是某个C结构的包装器。
该类有一个静态vector<Window*> windows_,它是一个包含已创建窗口的列表。
Window构造函数做了两件事:

  • handle_ = SDL_CreateWindow( ... );基本上分配C结构并将指针存储在成员变量handle_中;
  • 在列表中推送this

Window析构函数执行三项操作,但前提是handle_不是nullptr

  • SDL_DestroyWindow()解除分配C结构;
  • 从列表中删除this
  • handle_ = nullptr;

然后,在我的main中,我将Window声明为局部变量。
当窗口收到CLOSE事件时,我会调用该窗口的析构函数。
然后,当窗口超出范围时,窗口的析构函数再次被调用,并且我收到分段错误。

我知道很明显地称析构函数是微妙的,但我不知道为什么。
所以问题是双重的:

为什么会崩溃?

我可以使用什么设计来避免调用析构函数?

4 个答案:

答案 0 :(得分:0)

你真的不应该使用显式自动存储调用析构函数,这是未定义的行为:

标准12.4 [class.dtor] / 15

  

一旦为对象调用析构函数,该对象就不再存在   存在;如果为a调用析构函数,则行为未定义   生命周期结束的对象(3.8)。 [示例:如果是析构函数   显式调用自动对象,块为   随后以通常会隐含的方式离开   破坏对象,行为未定义。 - 示例]

而不是寻找一种禁用自动析构函数调用的“hacky”方式,或者你应该寻找更好的设计。

创建一个DestroyWindow()成员函数来处理清理并在类中设置一个标志,但让析构函数检查该标志并销毁窗口(+其他清理)如果没有手动完成已经解决了很多问题。


为了清楚起见,您可能希望它像标准库中的fstream和其他流一样。你可以显式地调用close,但是如果你不这样做,它会在析构函数中为你完成,以免泄漏资源。但请注意,此不应通过显式析构函数调用来完成。 (fstream例如有close成员函数明确关闭它。)

答案 1 :(得分:0)

调用析构函数很精细,因为它使对象处于未定义状态。此对象的任何读取都是未定义的。特别是,第二次调用析构函数 - 这将始终发生在局部变量上。

我认为在你的情况下,你将列表中的操作与窗口上的操作混合在一起。

构造函数应该只稳定当前对象上的不变量。在正常情况下,将它放在列表中不应该是构造函数的责任。

不要在CLOSE上调用析构函数。取消分配结构,并将nullptr放入_handler。这就是全部。

答案 2 :(得分:0)

您应该发布一些代码,以便我们可以准确地看到发生了什么,但是您不应该手动调用析构函数,因为这会导致未定义的行为(请参阅Raphael Miedl的回答)。而是向类中添加一个方法来关闭窗口,并使用该类为SDL窗口函数提供安全的接口。这是一幅草图:

class Window {
private:
    SDL_Window *window_;

public:
    Window() : window_(nullptr) { }
    Window(const Window &) = delete;
    Window(Window &&w) : window_(w.window_) { w.window_ = nullptr; }
    Window(const char* title, int x, int y, int w, int h, Uint32 flags)
        : window(SDL_CreateWindow(title, x, y, w, h, flags))
    { }

    ~Window()
    {
        if (window_ != nullptr)
            SDL_DestroyWindow(window_);
    }

    Window &operator=(const Window &) = delete;
    Window &operator=(Window &&w)
    {
        if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; }
        window_ = w.window_;
        w.window_ = nullptr;
    }

    operator bool() const
    {
        return window_ != nullptr;
    }

    void close()
    {
        if (window_ != nullptr) {
            SDL_DestroyWindow(window_);
            window_ = nullptr;
        }
    }
};

答案 3 :(得分:0)

您的代码需要保持状态,以确定您的资源是否已清理。通常,您将句柄设置为-1或指向nullptr的指针:

class MyResourceContainer {
    MyResourceContainer(int someParameter) {
        open(someParameter);
    }

    ~MyResourceContainer() {
        close();
    }

    void open(int someParameter) {
        if(handle > -1) {
            // throw exception or close(), whatever you prefer
        } 

        // allocate resource using specified parameter
        handle = 42;
    }

    // can be called multiple times
    void close() {
        if(handle > -1) {
            // release resource
            handle = -1;
        }
    }

private:
    int handle = -1;
};