简体   繁体   English

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

[英]How can I call a function “between” the constructors of private members?

I am writing a class to manage GLEW and GLFW for an OpenGL application. 我正在编写一个类来管理OpenGL应用程序的GLEW和GLFW。 The frameworks need to be initialized in a particular order. 框架需要按特定顺序初始化。 The correct order is this: 正确的顺序是这样的:

#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

My class initializes the frameworks in its constructor: 我的类在其构造函数中初始化框架:

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);
    }
};

I had to resort to GLEW and GLFW dummy structs to call the gl*wInit functions at the right time. 我不得不求助于GLEWGLFW伪结构来在正确的时间调用gl*wInit函数。 This works, but is very brittle. 这有效,但非常脆弱。 The fact that my program's correctness depends on the precise order of the private members worries me. 我的程序正确性取决于私人成员的确切顺序这一事实让我担心。

It's important that glfwInit and glewInit are each called exactly once throughout the lifetime of the application, but I might create many instances of the Window and Program classes. 重要的是glfwInitglewInit在应用程序的整个生命周期中都被调用一次,但我可能会创建WindowProgram类的许多实例。 It's assumed that the Engine class will be instantiated exactly once as well. 假设Engine类也将实例化一次。

Is there a better way to do this? 有一个更好的方法吗? I specifically want to keep the framework initialization inside the Engine class. 我特别希望将框架初始化保留在Engine类中。 Equivalently, I don't want to, say, move the call to glfwInit inside Window::Window (that wouldn't make any sense since many windows may be created but glfwInit should be called exactly once). 同样地,我不想,比如说,将调用移动到Window::Window glfwInit (这没有任何意义,因为可能会创建许多窗口,但glfwInit应该只调用一次)。

In C++, initialization is the job of constructors, and cleanup is the job of destructors. 在C ++中,初始化是构造函数的工作,而清理是析构函数的工作。 For each successful initialization you generally want a corresponding cleanup action. 对于每个成功的初始化,您通常需要相应的清理操作。 The language mechanism automates that. 语言机制使其自动化。

Using this language-provided support is known as RAII, Resource Acquisition Is Initialization . 使用这种语言提供的支持称为RAII, 资源获取是初始化

  • Create a C++ class for each (kind of) thing to be initialized & cleaned up. 为要初始化和清理的每种(类型)事物创建一个C ++类。
  • Express relationships for initialization orders, as constructor arguments, or as base classes. 表示初始化顺序,构造函数参数或基类的关系。
  • For initialization failure, throw an exception (so that there's no instance of this class). 对于初始化失败,抛出异常(以便没有此类的实例)。

Since the OpenGL stuff uses error codes you might consider std::system_error rather than just std::runtime_error ; 由于OpenGL的东西使用错误代码,你可能会考虑std::system_error而不仅仅是std::runtime_error ; the former (available since C++11) carries an error code. 前者(自C ++ 11起可用)带有错误代码。

Since glfwInit needs to be called once, globally, I would automate that call. 由于glfwInit需要在全局范围内调用一次,因此我会自动调用该调用。 One way is to place a static variable with initialization, inside a constructor of a class, with the header file declaring a dummy constant of that class. 一种方法是在类的构造函数中放置一个带初始化的static变量,头文件声明该类的虚拟常量。 This ensures a single common initialization across compilation units. 这确保了跨编译单元的单个公共初始化。

If all you want to do is have actions performed only for the first Window instance created, you can use a static local: 如果您只想为创建的第一个Window实例执行操作,则可以使用静态local:

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

If you want it done before Window's own initializer list, do it in a base class of Window . 如果你想让它之前窗口自己的初始化列表中完成的,做一个基类的Window

When you have implicit order dependencies, you can always make them explicit using constructor parameters or other syntactic constraits. 当您具有隐式顺序依赖关系时,您始终可以使用构造函数参数或其他语法约束使它们显式化。 Make your GLFW and GLEW structs proper classes instead of internal dummies. 使你的GLFWGLEW结构适当的类而不是内部的假人。 Have your Window class require a GLFW parameter in the constructor. Window类在构造函数中需要GLFW参数。 Have your GLEW class require a Window parameter in the constructor. 让你的GLEW类在构造函数中需要一个Window参数。 Then you have no choice but to make sure you have those things available. 然后你别无选择,只能确保你有这些东西。

Finally, turn on the compiler warning that enforces correct initializer order in the constructor, and make it an error. 最后,打开编译器警告,在构造函数中强制执行正确的初始化顺序,并使其成为错误。 Otherwise the error could still slip by undetected. 否则,错误仍然可以在未检测到的情况下滑落。

Rather than use your struct hack, it is almost always best to initialize your members explicitly: 而不是使用您的结构黑客,几乎总是最好明确初始化您的成员:

...
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);
    }

... ...

Having your program rely on order of member objects is not a bad thing at all, and quite common in C++ code. 让程序依赖于成员对象的顺序并不是件坏事,在C ++代码中很常见。 If you are really worried, you can enable warnings to trigger if you do it wrong (in GCC: -Wreorder). 如果你真的很担心,你可以启用警告,如果你做错了就会触发(在GCC:-Wreorder)。 The order they are declared, is the order they should be initialized. 它们被声明的顺序是它们应该被初始化的顺序。

What you have isn't bad, but I understand your concern.... 你有什么不错,但我理解你的关注....

One option would be to defer the window initialisation to the constructor body, which lets you use simple function calls for the other initialisation steps. 一种选择是将窗口初始化推迟到构造函数体,这允许您对其他初始化步骤使用简单的函数调用。 Add to class Window a constructor that other client code couldn't call accidentally, that clearly documents deferred initialisation: class Window添加一个其他客户端代码无法意外调用的构造函数,这显然是文档延迟初始化:

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

Then inside Engine : 然后在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();
}

Alternatively, std::unique_ptr<Window>, std::optional , boost::optional` could all be used to defer construction. 或者, std::unique_ptr<Window>, std :: optional , boost :: optional`都可以用于推迟构造。


Yet another option is to use a class to more obviously tie these member objects together, so they're unlikely to be accidentally reordered during code maintenance / evolution. 另一种选择是使用类来更明显地将这些成员对象绑定在一起,因此在代码维护/演进期间它们不太可能被意外重新排序。 Sadly, I can't see anything in the C++11 Standard guaranteeing std::tuple<> 's order of construction, but you could easily create something similar - perhaps called eg Ordered_Construction to communicate the intent. 遗憾的是,我在C ++ 11标准中看不到保证 std::tuple<>的构造顺序,但你可以很容易地创建类似的东西 - 也许叫做Ordered_Construction来传达意图。 This would tend to make access to the data members a little less convenient.... 这往往会使访问数据成员变得不那么方便....


You could also check the documentation for the error codes returned when the initialisation isn't done correctly, and make sure they trigger the program to close down in an orderly and impossible-to-ignore way if the initialisation was broken by a code change. 您还可以检查文档中的初始化未正确完成时返回的错误代码,并确保它们触发程序以有序且不可忽略的方式关闭,如果初始化被代码更改破坏。 Hopefully that would ensure any mistake was noticed before the code was released to users.... 希望这可以确保在将代码发布给用户之前发现任何错误....

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM