我有一个Window
类,它是某个C结构的包装器。
该类有一个静态vector<Window*> windows_
,它是一个包含已创建窗口的列表。
Window
构造函数做了两件事:
handle_ = SDL_CreateWindow( ... );
基本上分配C结构并将指针存储在成员变量handle_
中; this
。 Window
析构函数执行三项操作,但前提是handle_
不是nullptr
:
SDL_DestroyWindow()
解除分配C结构; this
。handle_ = nullptr;
然后,在我的main
中,我将Window
声明为局部变量。
当窗口收到CLOSE
事件时,我会调用该窗口的析构函数。
然后,当窗口超出范围时,窗口的析构函数再次被调用,并且我收到分段错误。
我知道很明显地称析构函数是微妙的,但我不知道为什么。
所以问题是双重的:
为什么会崩溃?
我可以使用什么设计来避免调用析构函数?
答案 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;
};