我对 c++ 容器的使用印象就是容器中最好存对象的指针,不要直接存放对象。看下面的代码片段,最好用 vec1 ,而 vec2 在添加对象到容器中时,会多一次复制。
struct container {
int a;
int b;
};
std::vector<container*> vec1;
std::vector<container> vec2;
所以我潜意识就是存放指针,不要直接存放对象。然后在实际开发中,有些不那么重要能满足需求的功能要是能采用更简洁的代码来实现,那就是太好了。上例中 vec1 在释放内存时还需要遍历 vector 释放元素指向的对象,而 vec2 则不必,代码就简洁一部分。
我在看 opengl 代码时看见一个例子。opengl 可以调用 glDrawArrays
绘制,也可以调用 glDrawElements
绘制,前者直接传入顶点数组数据。后者传入的是索引,若有相同的顶点数据则它们的索引是相同的,就有不必传送相同数据的效果。我看见的代码就是如何从一坨顶点数据中找到相同的顶点数据,并生成一个索引数组。举个例子若有 4 个顶点,坐标分别为 (0, 0, 0), (1, 1, 1), (0, 0, 0), (2, 2, 2) 。使用索引时顶点数据就简化成 (0, 0, 0), (1, 1, 1), (2, 2, 2) 而顶点索引数据则是 {0, 1, 2, 0} 。简单来说就是找出相同的顶点数据。
下面把那个 opengl 例子简化了,只关注如何找出相同的顶点数据。具体代码如下。
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <vector>
struct vec3 {
float x;
float y;
float z;
};
struct vec2 {
float x;
float y;
};
struct PackedVertex {
vec3 position;
vec2 uv;
// 最妙的地方。
bool
operator<(const PackedVertex that) const {
return memcmp((void*)this, (void*)&that, sizeof(PackedVertex)) > 0;
}
std::string
str() const {
char buff[100];
snprintf(buff, 100, "(%f,%f,%f)(%f,%f)", position.x, position.y, position.z, uv.x, uv.y);
std::string str(buff);
return str;
}
};
short
get_index(
PackedVertex &packed,
std::map<PackedVertex, short> &vertex_to_index) {
std::map<PackedVertex, short>::iterator it = vertex_to_index.find(packed);
if (it == vertex_to_index.end()) {
return -1;
} else {
return it->second;
}
}
vec3
fill_vec3(float x, float y, float z) {
vec3 v3 = {x, y, z};
return v3;
}
vec2
fill_vec2(float x, float y) {
vec2 v2 = {x, y};
return v2;
}
int
main() {
std::vector<vec3> vertices;
std::vector<vec2> uvs;
vertices.push_back(fill_vec3(1, 1, 1)); // [0]
uvs.push_back(fill_vec2(0.1, 0.1));
vertices.push_back(fill_vec3(2, 2, 2)); // [1]
uvs.push_back(fill_vec2(0.2, 0.2));
vertices.push_back(fill_vec3(3, 3, 3)); // [2]
uvs.push_back(fill_vec2(0.3, 0.3));
vertices.push_back(fill_vec3(2, 2, 2));
uvs.push_back(fill_vec2(0.2, 0.2));
vertices.push_back(fill_vec3(1, 1, 1));
uvs.push_back(fill_vec2(0.1, 0.1));
vertices.push_back(fill_vec3(4, 4, 4)); // [3]
uvs.push_back(fill_vec2(0.4, 0.4));
vertices.push_back(fill_vec3(4, 4, 4));
uvs.push_back(fill_vec2(0.4, 0.4));
std::map<PackedVertex, short> vertex_to_index;
short index;
for (unsigned int i = 0; i < vertices.size(); i++) {
PackedVertex packed = {vertices[i], uvs[i]};
index = get_index(packed, vertex_to_index);
if (index < 0) {
index = vertex_to_index.size();
vertex_to_index[packed] = index;
}
}
std::map<PackedVertex, short>::iterator iter = vertex_to_index.begin();
for (; iter != vertex_to_index.end(); iter++) {
printf("%s %d\n", iter->first.str().c_str(), iter->second);
}
return 0;
}
使用 map 容器时,需要元素可以比较,例子中重载 <
操作符真的让我很惊喜。这段代码没有使用指针,因此也不需要显示的释放内存,虽然添加对象到 map 容器中时,会复制 PackedVertex
对象,但是有些场合这种代码还是能满足需求的。
编译。 g++ -o test test.cpp -static 执行结果如下。
(4.000000,4.000000,4.000000)(0.400000,0.400000) 3
(1.000000,1.000000,1.000000)(0.100000,0.100000) 0
(3.000000,3.000000,3.000000)(0.300000,0.300000) 2
(2.000000,2.000000,2.000000)(0.200000,0.200000) 1