[英]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. 我不得不求助于
GLEW
和GLFW
伪结构来在正确的时间调用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. 重要的是
glfwInit
和glewInit
在应用程序的整个生命周期中都被调用一次,但我可能会创建Window
和Program
类的许多实例。 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, 资源获取是初始化 。
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. 使你的
GLFW
和GLEW
结构适当的类而不是内部的假人。 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.