C++:使用RAII解析构造函数-初始化器列表依赖项
C++: Resolve Constructor Initializer List Dependencies with RAII
要安全地设置,一件特别棘手的事情是GLX。问题是,在初始化过程中的任何时候出现错误时,都必须按照正确的顺序分配和释放相当多的资源。
以下是我用C写的内容(仅相关部分)。
int gfx_init(struct gfx *g)
{
int vis_attriblist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
XSetWindowAttributes wa;
XVisualInfo *vis_info;
int r = 0;
g->xdpy = XOpenDisplay(NULL);
if (g->xdpy == NULL) {
r = -1;
LOG_ERROR("Could not open X Display");
goto xopendisplay_failed;
}
vis_info = glXChooseVisual(g->xdpy, DefaultScreen(g->xdpy),
vis_attriblist);
if (vis_info == NULL) {
r = -1;
LOG_ERROR("Couldn't get an RGBA, double-buffered visual"
" (GLX available?)n");
goto glxchoosevisual_failed;
}
g->xcolormap = XCreateColormap(g->xdpy, DefaultRootWindow(g->xdpy),
vis_info->visual, AllocNone);
if (gfx_has_xerror(g) /* Checks if there are errors
by flushing Xlib's protocol buffer
with a custom error handler set.
Not included here */) {
r = -1;
LOG_ERROR("Failed to create colormap");
goto xcreatecolormap_failed;
}
wa.colormap = g->xcolormap;
wa.event_mask = StructureNotifyMask | VisibilityChangeMask;
g->xwindow = XCreateWindow(g->xdpy, DefaultRootWindow(g->xdpy),
0,0,1280,1024, 0, vis_info->depth,
InputOutput, vis_info->visual, CWColormap |
CWEventMask, &wa);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("Failed to create X11 Window");
goto xcreatewindow_failed;
}
g->glxctx = glXCreateContext(g->xdpy, vis_info, NULL, True);
if (g->glxctx == NULL) {
r = -1;
LOG_ERROR("Failed to create GLX context");
goto glxcreatecontext_failed;
}
if (glXMakeCurrent(g->xdpy, g->xwindow, g->glxctx) == False) {
r = -1;
LOG_ERROR("Failed to make context current");
goto glxmakecurrent_failed;
}
g->xa_wmdeletewindow = XInternAtom(g->xdpy, "WM_DELETE_WINDOW", False);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("XInternAtom failed");
goto xinternatom_failed;
}
XSetWMProtocols(g->xdpy, g->xwindow, &g->xa_wmdeletewindow, 1);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("XSetWMProtocols failed");
goto xsetwmprotocols_failed;
}
glClearColor(1,1,1,1);
glColor4f(0,0,0,1);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (glGetError() != GL_NO_ERROR) {
r = -1;
LOG_ERROR("There have been GL errors");
goto gotglerror;
}
XMapWindow(g->xdpy, g->xwindow);
XFlush(g->xdpy);
if (r < 0) {
gotglerror:
xsetwmprotocols_failed:
xinternatom_failed:
glXMakeCurrent(g->xdpy, None, NULL);
glxmakecurrent_failed:
glXDestroyContext(g->xdpy, g->glxctx);
glxcreatecontext_failed:
XDestroyWindow(g->xdpy, g->xwindow);
xcreatewindow_failed:
XFreeColormap(g->xdpy, g->xcolormap);
}
xcreatecolormap_failed:
/* This is a local resource which must be destroyed
in case of success as well */
XFree(vis_info);
if (r < 0) {
glxchoosevisual_failed:
XCloseDisplay(g->xdpy);
}
xopendisplay_failed:
return r;
}
事实上我很满意。我认为这是很好的C风格。唯一的问题是,对于gfx_destroy
函数,gfx_init
的释放部分的代码必须有点重复,但这非常简单。
我想知道的是如何以良好的RAII C++风格进行初始化。特别是,成员分配之间存在依赖关系,想象中的RAII class Gfx
的构造函数应该按照正确的顺序初始化,或者抛出异常,并确保最初构造的部分再次被拆除。
因此,自然的过程是为分配的类型编写简短的包装器。例如
struct MyDisplay {
Display *dpy;
MyDisplay() : dpy(XOpenDisplay(NULL)) { if (!dpy) throw "XOpenDisplay()"; }
~MyDisplay() { XCloseDisplay(dpy); }
};
struct MyXVisualInfo {
XVisualInfo *info;
static int vis_attriblist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
MyXVisualInfo(Display *dpy)
: info(glXChooseVisual(dpy, DefaultScreen(dpy), vis_attriblist) {
if (!info)
throw "glXChooseVisual()";
}
~MyXVisualInfo() {
XFree(info);
}
};
而Gfx
类:
class Gfx {
MyDisplay mydpy_;
MyXVisualInfo myinfo_;
/* ... */
public:
Gfx::Gfx() : mydpy_(), myinfo_(mydpy_.dpy) /* , ... */ {}
};
但在这一点上,我们有一个问题:(这不是真的,请参阅@MarkB的答案)myinfo_(mydpy.dpy)
实际上将一个未定义的值交给了MyXVisualInfo构造函数。这是RAII班成员的一个亮点吗
此外,如果构造函数需要分配临时资源,就像myinfo_
的情况一样,我不想将其存储在类中,我认为没有办法避免进入构造函数主体,并使用构造函数主体本地的资源从那里分配给成员。这意味着成员将经历额外的构建和解构,这是不可能的,因为这会产生副作用。
我唯一能想到的就是使用unique_ptr
:
class Gfx {
unique_ptr<MyDisplay> mydpy_;
unique_ptr<MyColormap> mycolormap_;
/* ... */
public:
Gfx() {
mydpy_.reset(new MyDisplay());
MyXVisualInfo myinfo(dpy);
mycolormap_.reset(new MyXColormap(mydpy_->dpy,
DefaultRootWindow(mydpy_->dpy),
myinfo->info, AllocNone));
}
};
现在,这显然被过度设计了,并且已经成为一个需要维护的杂烩。此外,引入具有不必要开销的unique_ptr也不好。
难道没有一种干净的方法可以像C版本那样以干净的RAII方式进行操作吗?
是什么导致您得出myinfo(mydpy.dpy)
将具有未定义行为的结论?您链接的SO问题与示例代码中的场景不同。请注意,在您的情况下,您按照希望初始化的顺序列出类定义中的成员,这样就不会有未定义的行为。
通常,如果你觉得你不能在初始化列表中做一些你需要的事情,并且需要使用构造函数主体,你可以将代码分解成一个函数,并在初始值设定项列表中使用该函数的返回值,但我很难理解Also, if the constructor needs to allocate temporary resources
之后你的意思,所以我不能给你更好的答案。
std::shared_ptr
和std::unique_ptr
可以与自定义deleter一起使用。示例:
std::shared_ptr<int> mem (static_cast<int*>(malloc(sizeof(int))), free);
或带有λ:
std::shared_ptr<int> mem (new int(), [](int* foobar) {
std::cout << "I am a deleter" << std::endl;
delete foobar;
});
- C++类 - 初始化列表 - 递归 - 按值传递
- 在初始化列表之外手动调用基类的构造函数
- C++:带有大括号初始化列表的函数调用表达式 - 标准是否规定在单个元素列表的微不足道的情况下忽略大括号?
- std::map与谓词与初始化列表
- 类内初始化与构造函数初始化列表的顺序
- 当返回语句时,逗号运算符、大括号初始化列表和 std::unique_ptr 组合在一起
- 使用初始化列表填充C++中的多维结构数组时出现问题
- 如何在初始化列表中的构造函数之后初始化变量/对象?
- C++初始化列表与分配值
- C++初始化列表中的向量集大小或调整大小
- 在构造函数初始化列表中使用 std::variant
- emplace_back初始化列表错误,当初始化列表在独立变量上工作时
- 解释了构造函数成员初始化列表
- 使用初始化列表时如何获取私有数据?
- 用初始化列表和超类构造函数声明子类构造函数的正确方式
- 如何在成员初始化列表中声明共享指针
- 庞大的初始化列表,如何修复"fatal error C1060: compiler is out of heap space"
- 我可以检查初始化列表中设置的构造函数主体中的变量吗
- 使用整数初始化列表初始化长双精度的向量
- 是否可以在C++中使用初始化列表设置数组的特定成员?