13. 资源的生命周期管理 + {} 限制局部作用域 发表于 2025-07-06 更新于 2025-07-06
OpenGL 13. 资源的生命周期管理 + {} 限制局部作用域 Rif 2025-07-06 2025-07-06 你观察得非常仔细,这个 { 和 } 构成的局部作用域(Local Scope)在这里起着一个至关重要的作用,它与 C++ 的一个核心特性——RAII (Resource Acquisition Is Initialization) 紧密相关,并且确实和你提到的栈分配 (Stack Allocation) 有关。
让我们来详细解释这个作用域的目的。
问题的核心:资源的生命周期管理 在你的代码中,你创建了一些封装了 OpenGL 对象的 C++ 类,比如:
VertexBuffer vb(...)
IndexBuffer ib(...)
这些类很可能是按照 RAII 原则设计的(这是 Cherno 教程中的一个关键点)。
RAII 的核心思想是 :
在对象的构造函数中获取资源 (比如调用 glGenBuffers 创建一个 VBO)。
在对象的析构函数中释放资源 (比如调用 glDeleteBuffers 删除这个 VBO)。
当一个对象是在栈 (Stack) 上创建时,它的生命周期就和它所在的作用域绑定了。一旦程序执行离开这个作用域(无论是正常结束还是因为异常跳出),这个对象就会被自动销毁 ,它的析构函数 (destructor) 就会被自动调用 。
分析你的代码中的作用域 我们来看看这个作用域内发生了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { unsigned int vao; GLCall (glGenVertexArrays (1 , &vao)); VertexBuffer vb (&pos, 8 * sizeof (float )) ; IndexBuffer ib (&indices, 6 ) ; unsigned int program = CreateShaderProgram (...); while (!glfwWindowShouldClose (window)) { } GLCall (glDeleteProgram (program)); } glfwTerminate ();
当程序执行到 } 时会发生什么?
栈对象逆序销毁 :程序执行流离开了这个局部作用域。所有在这个作用域的栈上创建的对象,会以它们创建时相反的顺序 被自动销 chiffres。
ib (IndexBuffer 对象) 首先被销毁,它的析构函数 ~IndexBuffer() 被自动调用 。在这个析构函数内部,很可能执行了 glDeleteBuffers(1, &m_RendererID) 来释放 IBO。
vb (VertexBuffer 对象) 接着被销毁,它的析构函数 ~VertexBuffer() 被自动调用 。在这个析构函数内部,执行了 glDeleteBuffers(1, &m_RendererID) 来释放 VBO。
vao 和 program 不受影响 :因为 vao 和 program 只是普通的 unsigned int,它们不是类对象,没有析构函数。它们的值只是从栈上被弹出,但它们所代表的 OpenGL 资源不会 被自动释放。这就是为什么你需要手动调用 glDeleteProgram(program)。你也应该在这里加上 glDeleteVertexArrays(1, &vao)。
这个作用域的核心作用 确保在 OpenGL 上下文 (Context) 被销毁之前,所有与该上下文相关的 GPU 资源都已经被正确释放。
让我们看看如果没有这个作用域会发生什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 VertexBuffer vb (...) ;IndexBuffer ib (...) ;glfwTerminate (); return 0 ;
所以,这个 {...} 的作用就是:
创建一个受控的生命周期 :它定义了一个明确的区域,所有基于 RAII 的 OpenGL 封装类的生命周期都限制在这个区域内。
保证正确的销毁顺序 :确保在 glfwTerminate() 被调用(销毁 OpenGL 上下文)之前 ,所有封装类的析构函数都已经被自动调用,从而安全地释放了 VBO, IBO 等 GPU 资源。
总结 这个局部作用域是利用 C++ RAII 特性来自动化和安全地管理 OpenGL 资源 的一种非常优雅和强大的技术。
它与栈分配 (Stack Allocation) 相关 :因为只有在栈上创建的对象才能在离开作用域时自动调用析构函数。
它解决了资源管理的核心痛点 :避免了手动调用 glDeleteBuffers 等函数,减少了因忘记释放资源而导致的内存泄漏,并保证了资源在正确的时间点被释放。
这就是现代 C++ 与 C-style API (如 OpenGL) 相结合的最佳实践之一。
提问内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 int main (void ) { GLFWwindow* window; if (!glfwInit ()) return -1 ; window = glfwCreateWindow (640 , 480 , "Hello World" , NULL , NULL ); glfwMakeContextCurrent (window); glfwSwapInterval (1 ); if (glewInit () != GLEW_OK) std::cerr << " Error init glew." << std::endl; { float pos[] = { -0.5f , -0.5f , +0.5f , -0.5f , +0.5f , +0.5f , -0.5f , +0.5f }; unsigned int indices[] = { 0 , 1 , 2 , 2 , 3 , 0 }; unsigned int vao; GLCall (glGenVertexArrays (1 , &vao)); GLCall (glBindVertexArray (vao)); VertexBuffer vb (&pos, 8 * sizeof (float )) ; unsigned int AttribIndex = 0 ; GLCall (glEnableVertexAttribArray (AttribIndex)); GLCall (glVertexAttribPointer (AttribIndex, 2 , GL_FLOAT, GL_FALSE, sizeof (float ) * 2 , (const void *)0 )); IndexBuffer ib (&indices, 6 ) ; ShaderProgramSource ShaderSource = ParseShader ("res/shaders/Basic.shader" ); unsigned int program = CreateShaderProgram (ShaderSource.VertexSource, ShaderSource.FragmentSource); GLCall (glUseProgram (program)); GLCall (int location = glGetUniformLocation (program, "u_Color" )); ASSERT (location != -1 ); float r = 0.0f ; float increment = 0.01f ; while (!glfwWindowShouldClose (window)) { GLCall (glClear (GL_COLOR_BUFFER_BIT)); GLCall (glUniform4f (location, r, 0.3f , 0.8f , 1.0f )); if (r > 1.0f || r < 0.0f ) increment = -increment; r += increment; GLCall (glBindBuffer (GL_ARRAY_BUFFER, 0 )); GLCall (glBindVertexArray (vao)); GLCall (glDrawElements (GL_TRIANGLES, 6 , GL_UNSIGNED_INT, nullptr )); GLCall (glfwSwapBuffers (window)); GLCall (glfwPollEvents ()); } GLCall (glDeleteProgram (program)); } glfwTerminate (); return 0 ; }
解释一下在这里的glfwTerminate之前的}和注释/* Because the */后面的{,这个局部作用域有什么作用。提示,这里好像有关于 stack alloac