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

时间:2013-11-26 15:12:21

标签: 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的构造函数应该以正确的顺序初始化,或抛出异常并保证初始构造的部分再次被拆除。

因此,自然进展是为分配的类型编写短包装器。 E.g。

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班级成员来说,这是一个showstopper吗? (这不是真的,请参阅@MarkB的回答

另外,如果构造函数需要分配临时资源,实际情况就是myinfo_我不希望存储在类中的情况,我认为没有办法避免进入构造函数体,< em>并使用构造函数体的本地资源从那里分配给成员。这意味着会员将接受额外的建设和解构,这是不可行的,因为涉及副作用。

我唯一能想到的就是使用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版本的工作?

2 个答案:

答案 0 :(得分:2)

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

通常,如果您觉得在初始化列表中无法执行某些操作,并且需要使用构造函数体,则可以将代码分解为函数并使用初始化列表中的返回值,但是我我很难理解你在Also, if the constructor needs to allocate temporary resources之后的意思,所以我不能给你一个比这更好的答案。

答案 1 :(得分:1)

std::shared_ptrstd::unique_ptr可与自定义删除器一起使用。例如:

std::shared_ptr<int> mem (static_cast<int*>(malloc(sizeof(int))), free);

或使用lambda:

std::shared_ptr<int> mem (new int(), [](int* foobar) {
    std::cout << "I am a deleter" << std::endl;
    delete foobar;
});