C++:使用 RAII 解决构造函数初始值设定项列表依赖关系

标签 c++ constructor 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) /* , ... */ {}
};

但此时我们遇到了一个问题:myinfo_(mydpy.dpy) 实际上是 hands an undefined value to the MyXVisualInfo constructor .这是对 RAII 类(class)成员的展示吗? (这不是真的,请参阅@MarkB 的回答)

此外,如果构造函数需要分配临时资源,例如 myinfo_ 的实际情况,我不想将其存储在类中,我认为没有办法避免进入构造函数body,并从那里分配给成员,使用构造函数主体的本地资源。这意味着成员将经历额外的构造和解构,这是不行的,因为涉及副作用。

我唯一能想到的就是使用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 也不好。

难道没有一种干净的方法可以以干净的 RAII 方式完成 C 版本所做的事情吗?

最佳答案

是什么让您得出 myinfo(mydpy.dpy) 将具有未定义行为的结论?您链接的 SO 问题不是您在示例代码中的场景。请注意,在您的情况下,您按照希望初始化的顺序列出类定义中的成员,因此没有未定义的行为。

通常如果你觉得你不能在初始化列表中做一些你需要的事情并且需要使用构造函数主体你可以交替地将代码分解成一个函数并使用初始化列表中的返回值但我'我真的很难理解你在之后的意思,此外,如果构造函数需要分配临时资源,所以我不能给你比这更好的答案。

关于C++:使用 RAII 解决构造函数初始值设定项列表依赖关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20220857/

相关文章:

c++ - 显式 C++ 宏扩展

python - Python 中的 RAII : What's the point of __del__?

c++ - 我的类的构造函数应该执行多少工作?

c++ - 如何获取 QListWidget::selectedItems() 的索引?

c++ - 如何将值附加到命令行参数数组?

c++ - 编译错误信息

c++ - 默认构造函数的宏是否可能,待定的编译器支持?

.net - Winforms 表单构造函数与加载事件

java - Java中的构造函数可以是私有(private)的吗?

rust - 非词法生命周期借用检查器是否会过早释放锁?