如何传递含指针数组或静态数组的结构体?
解决静态/动态数组版VertexBufferData的代码复用问题
我完全懂你的痛点——两个结构体核心逻辑一模一样,就因为最后两个成员是静态数组还是动态指针,就得写两套几乎相同的绑定、上传函数,太冗余了。下面给你几个轻量的C++方案,都能避开继承、虚函数,还能保持结构体的POD特性:
方案1:模板+类型萃取(类型安全的首选)
其实模板是可以解决这个问题的,关键是用类型萃取统一两种数据类型的访问方式,不用改结构体本身:
首先写一个类型萃取工具,分别处理静态数组和动态指针:
// 基础模板 template<typename T> struct BufferTraits; // 特化:处理静态数组,直接返回数组大小和指针 template<typename T, size_t N> struct BufferTraits<T[N]> { static constexpr size_t get_capacity(const T(&)[N]) { return N; } static T* get_data(T(&arr)[N]) { return arr; } }; // 特化:处理动态指针,从结构体的_cap字段拿容量 template<typename T> struct BufferTraits<T*> { static size_t get_capacity(const T*, size_t cap) { return cap; } static T* get_data(T* ptr) { return ptr; } };
然后把你的处理函数改成模板函数,自动适配两种结构体:
template<typename VBD> void gl_bind_buffers_and_upload_data(VBD* vbd, GLenum usage) { // 处理顶点缓冲区 glBindBuffer(GL_ARRAY_BUFFER, vbd->vbo.id); auto vert_ptr = BufferTraits<decltype(vbd->vertices)>::get_data(vbd->vertices); size_t vert_cap = BufferTraits<decltype(vbd->vertices)>::get_capacity(vbd->vertices, vbd->v_cap); glBufferData(GL_ARRAY_BUFFER, vert_cap * sizeof(GLfloat), vert_ptr, usage); glBufferSubData(GL_ARRAY_BUFFER, 0, vbd->v_count * sizeof(GLfloat), vert_ptr); // 处理索引缓冲区 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbd->ebo.id); auto index_ptr = BufferTraits<decltype(vbd->indices)>::get_data(vbd->indices); size_t index_cap = BufferTraits<decltype(vbd->indices)>::get_capacity(vbd->indices, vbd->i_cap); glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_cap * sizeof(GLuint), index_ptr, usage); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, vbd->i_count * sizeof(GLuint), index_ptr); }
这个方案完全是编译期处理,不会给结构体加任何额外开销,类型安全还能自动推导,是最推荐的方案。
方案2:宏定义生成结构体和逻辑(极简C风格)
如果你不想用模板,宏是个直接的选择,通过宏参数控制数组类型,避免重复写结构体:
// 宏定义通用的VertexBufferData结构体 #define DEFINE_VBD(VERT_TYPE, INDEX_TYPE) \ struct VertexBufferData_##VERT_TYPE##_##INDEX_TYPE { \ VertexBuffer vbo; \ ElementBuffer ebo; \ size_t v_cap; \ size_t v_count; \ size_t i_cap; \ size_t i_count; \ VERT_TYPE vertices; \ INDEX_TYPE indices; \ }; // 生成动态指针版本 DEFINE_VBD(GLfloat*, GLuint*) // 生成静态数组版本(替换成你的常量大小) DEFINE_VBD(GLfloat[100], GLuint[200]) // 通用的绑定上传宏,自动适配两种结构体 #define GL_BIND_UPLOAD(vbd_ptr, usage) do { \ glBindBuffer(GL_ARRAY_BUFFER, (vbd_ptr)->vbo.id); \ GLfloat* verts = static_cast<GLfloat*>(static_cast<void*>(&(vbd_ptr)->vertices)); \ glBufferData(GL_ARRAY_BUFFER, (vbd_ptr)->v_cap * sizeof(GLfloat), verts, usage); \ glBufferSubData(GL_ARRAY_BUFFER, 0, (vbd_ptr)->v_count * sizeof(GLfloat), verts); \ \ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (vbd_ptr)->ebo.id); \ GLuint* indices = static_cast<GLuint*>(static_cast<void*>(&(vbd_ptr)->indices)); \ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (vbd_ptr)->i_cap * sizeof(GLuint), indices, usage); \ glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, (vbd_ptr)->i_count * sizeof(GLuint), indices); \ } while(0)
这个方案完全基于C的特性,没有C++模板的复杂度,结构体依然是POD,缺点是宏的可读性稍差,但胜在简单直接。
方案3:利用内存偏移统一访问(纯C风格,无模板宏)
如果连模板和宏都不想碰,我们可以利用结构体的内存布局一致性——两个结构体前6个成员完全一样,偏移量相同,用offsetof来定位最后两个成员:
// 定义一个只包含前6个成员的"基准结构体",用来计算偏移 struct VBD_Base { VertexBuffer vbo; ElementBuffer ebo; size_t v_cap; size_t v_count; size_t i_cap; size_t i_count; }; // 通用处理函数,接受void*,通过偏移获取数据 void gl_bind_buffers_and_upload_data(void* vbd_raw, GLenum usage) { VBD_Base* base = static_cast<VBD_Base*>(vbd_raw); // 计算vertices的内存偏移(用任意一个结构体来获取偏移量) const size_t vert_offset = offsetof(VertexBufferData, vertices); GLfloat* verts = static_cast<GLfloat*>(static_cast<char*>(vbd_raw) + vert_offset); glBindBuffer(GL_ARRAY_BUFFER, base->vbo.id); glBufferData(GL_ARRAY_BUFFER, base->v_cap * sizeof(GLfloat), verts, usage); glBufferSubData(GL_ARRAY_BUFFER, 0, base->v_count * sizeof(GLfloat), verts); // 同理处理indices const size_t index_offset = offsetof(VertexBufferData, indices); GLuint* indices = static_cast<GLuint*>(static_cast<char*>(vbd_raw) + index_offset); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, base->ebo.id); glBufferData(GL_ELEMENT_ARRAY_BUFFER, base->i_cap * sizeof(GLuint), indices, usage); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, base->i_count * sizeof(GLuint), indices); }
这个方案完全不需要模板或宏,只要保证两个结构体的成员顺序一致,就能无缝适配。唯一要注意的是,如果你修改了结构体的成员顺序,得同步更新偏移量的计算。
总结
这三个方案都能帮你避免重复代码,而且都满足你的要求:保持POD、无继承虚函数、轻量实现。优先推荐模板+类型萃取的方案,类型安全且维护性好;如果偏爱C风格,宏或偏移量方案也能搞定。
内容的提问来源于stack exchange,提问作者synchronizer
相关产品推荐
相关产品推荐

