C++:使用RAII解析构造函数-初始化器列表依赖项

C++: Resolve Constructor Initializer List Dependencies with RAII

本文关键字:初始化 列表 依赖 构造函数 使用 RAII C++      更新时间:2023-10-16

要安全地设置,一件特别棘手的事情是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)实际上将一个未定义的值交给了MyXVisualInfo构造函数。这是RAII班成员的一个亮点吗(这不是真的,请参阅@MarkB的答案

此外,如果构造函数需要分配临时资源,就像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_ptrstd::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;
});