如何在私有成员的构造函数之间调用函数?

时间:2015-02-23 03:31:42

标签: c++ constructor glfw glew

我正在编写一个类来管理OpenGL应用程序的GLEW和GLFW。框架需要按特定顺序初始化。正确的顺序是:

#include <GL/glew.h>
#include <GLFW/glfw3.h>

glfwInit();
GLFWwindow* w = glfwCreateWindow(1280, 720, "main", 0, 0);
glfwMakeContextCurrent(w);
glewInit();
// GL API is now available

我的类在其构造函数中初始化框架:

class Engine
{
    struct GLEW { GLEW() { glewInit(); } };
    struct GLFW { GLFW() { glfwInit(); } };

    GLFW m_glfw;
    // Window::Window calls glfwCreateWindow and glfwMakeContextCurrent 
    Window m_window; 
    GLEW m_glew;
    // Program::Program requires GL API
    Program m_program; 

public:
    // a simplified version of the real ctor
    Engine(int width, int height, const char* name)
        : m_window(width, height, name)
    {
        m_window.setWindowUserPointer(this);
    }
};

我不得不求助于GLEWGLFW虚拟结构来在正确的时间调用gl*wInit函数。这有效,但非常脆弱。我的程序的正确性取决于私人成员的确切顺序这一事实让我担心。

在应用程序的整个生命周期中,glfwInitglewInit每次都被调用一次非常重要,但我可能会创建Window和{{1}的许多实例类。它假设Program类也将被实例化一次。

有更好的方法吗?我特别希望将框架初始化保留在Engine类中。同样地,我不想,比如说,将Engine内部的glfwInit移到Window::Window内(由于可能会创建许多窗口,但glfwInit可能没有任何意义。应该只调用一次。)

5 个答案:

答案 0 :(得分:1)

在C ++中,初始化是构造函数的工作,而清理是析构函数的工作。对于每个成功的初始化,您通常需要相应的清理操作。语言机制使其自动化。

使用此语言提供的支持称为RAII,资源获取是初始化

  • 为要初始化的每种(某种)事物创建一个C ++类。清理干净。
  • 表示初始化顺序,构造函数参数或基类的关系。
  • 对于初始化失败,抛出一个异常(这样就没有这个类的实例)。

由于OpenGL内容使用错误代码,您可能会考虑std::system_error而不仅仅是std::runtime_error;前者(自C ++ 11起可用)带有错误代码。

由于glfwInit需要在全局范围内调用一次,我会自动调用该调用。一种方法是在类的构造函数中放置一个带有初始化的static变量,并使用头文件声明该类的虚拟常量。这确保了跨编译单元的单个公共初始化。

答案 1 :(得分:1)

如果您只想对所创建的第一个Window实例执行操作,则可以使用静态本地:

Window::Window()
{
    static auto once = (glfwInit(), true);
    // other stuff
} 

如果您希望在Window自己的初始化列表之前完成,请在Window的基类中完成。

答案 2 :(得分:1)

当您具有隐式顺序依赖关系时,您始终可以使用构造函数参数或其他语法约束使它们显式化。让你的GLFWGLEW结构适当的类而不是内部的假人。让Window类在构造函数中需要GLFW参数。让GLEW类在构造函数中需要Window参数。然后你别无选择,只能确保你有这些东西。

最后,打开编译器警告,在构造函数中强制执行正确的初始化顺序,并使其成为错误。否则,错误仍然可以在未检测到的情况下滑落。

答案 3 :(得分:0)

不是使用你的struct hack,最好是明确地初始化你的成员:

...
public:
    // a simplified version of the real ctor
    Engine(int width, int height, const char* name)
        : m_glfw(initGlfw()),
        , m_window(width, height, name),
        , m_glew(initGlew())
    {
        m_window.setWindowUserPointer(this);
    }

...

让你的程序依赖于成员对象的顺序并不是件坏事,在C ++代码中很常见。如果你真的很担心,你可以启用警告,如果你做错了就会触发(在GCC:-Wreorder)。它们被声明的顺序是它们应该被初始化的顺序。

答案 4 :(得分:0)

你所拥有的并不是坏事,但我理解你的担忧......

一种选择是将窗口初始化推迟到构造函数体,这允许您对其他初始化步骤使用简单的函数调用。添加到class Window一个构造函数,其他客户端代码无法无意中调用,这清楚地记录了延迟初始化:

enum Construction { Deferred_Init };
explicit Window::Window(Init);
void init(int width, int height, const char* name);

然后在Engine内:

Engine(int width, int height, const char* name)
    : m_window(Deferred_Init)
{
    glfwInit();
    m_window.init(width, height, name);
    m_window.setWindowUserPointer(this);
    glewInit();
}

或者,std::unique_ptr<Window>, std :: optional , boost :: optional`都可以用来推迟构造。


另一个选择是使用类来更明显地将这些成员对象绑定在一起,因此在代码维护/演进期间它们不太可能被意外重新排序。遗憾的是,我无法在C ++ 11标准保证 std::tuple<>的构造顺序中看到任何内容,但您可以轻松地创建类似的内容 - 例如Ordered_Construction传达意图。这往往会使访问数据成员的方便一点......


如果初始化没有正确完成,您还可以检查文档中返回的错误代码,并确保它们在初始化被破坏时以有序且不可忽视的方式触发程序关闭通过代码更改。希望这可以确保在代码发布给用户之前发现任何错误....