C++ 标准库の使用迷思
转眼 2020 已经过去,回顾来看总有些迷幻,新年之际,自己倒不打算写些什么年终总结,想想还是记一小篇技术小文: C++ 标准库的使用迷思.
了解 C++ 的朋友对于标准模板库肯定不陌生,平日用 C++ 开发时基本也是离不开标准模板库的,举个最普遍的例子:当我们需要动态数组时,甚至于仅需要静态数组时,我们都会第一个想到 vector :
#include <vector>
std::vector<int> int_array;
原因就在于使用 vector 方便且稳定,不用自己处理数组扩容等问题,久而久之,我们总会产生个固有印象:使用 C++ 开发离不开 STL(Standard Template Library,即标准模板库).
但是如果你全面了解过人们关于使用 STL 的各类看法,尤其是游戏行业人员关于 STL 使用的看法,你就会发现,不少人其实是反对使用 STL 的!
这就有点像 2020 年了,颇有些迷幻的味道:一方面有人觉得 STL 方便稳定,应该多多应用;另一方面又有人对 STL 避之不及,似乎其问题多多.
支持 STL 的理由在此不再赘述,大家应该都有所耳闻,我们这里主要来看看反对 STL 的各类观点(有一些观点,譬如 STL 跨平台支持不完善, STL 实现效率低等等,目前已经有了很大改善,可以认为已经不是问题了,下面不再列出):
从 STL 自身出发:
- STL allocator 难以正确定制,缺少设置内存对齐(alignment)等功能
- STL container 同样难以定制,并且存在不能保存引用,存在额外的内存开销(容器为空的情况下)等问题
- STL 难以调试
- STL 缺少某些需要的功能
- …
从不使用 STL 的优点出发:
- 可以对每处细节进行定制,优化实现效率,内存使用等方面的表现
- 跨平台表现更加一致
- 易于调试
- 可以扩展更多功能
- …
这里举个简单的例子,假设你现在需要维护一个集合数据(集合中的元素不能重复),你会选择怎样实现呢?
熟稔的朋友可能马上会想到 unordered_set:
#include <unordered_set>
std::unordered_set<int> std_unordered_set;
接着,你可能会发现集合元素的数量其实不多,需要的操作也很少(仅需要增删查),你当然可以继续沿用 unordered_set,但是你也可以定制一个更适用你当前使用场景的 unordered_set_lite:
template<typename T, size_t capacity = 16>
class unordered_set_lite {
public:
bool insert(T value) {
for (size_t i = 0; i < m_count; ++i) {
if (m_data[i] == value) {
return false;
}
}
if (m_count < capacity) {
m_data[m_count] = value;
++m_count;
return true;
}
return false;
}
bool erase(T value) {
for (size_t i = 0; i < m_count; ++i) {
if (m_data[i] == value) {
m_data[i] = m_data[m_count - 1];
--m_count;
return true;
}
}
return false;
}
bool contains(T value) const {
for (size_t i = 0; i < m_count; ++i) {
if (m_data[i] == value) {
return true;
}
}
return false;
}
size_t size() const {
return m_count;
}
private:
T m_data[capacity];
size_t m_count = 0;
};
unordered_set_lite 对比 unordered_set 有如下优点:
- 更轻量(少)的代码实现
- 更紧凑(少)的内存使用
- 更高的运行效率(有 6 倍左右的提速)
在游戏业界, EASTL 应该是用于替换(不使用) STL 最知名的一个程序库了,有兴趣的朋友可以看看.另外的,了解 UE 的朋友可能也知道,在虚幻引擎中也使用了自己实现的一套模板库(TArray,TMap 等等),而没有使用 STL,曾有人就为何 UE 不使用 STL 提出过疑问,得到的答案大抵和上面的提到的观点相似,不过最后 Tim Sweeney 道出了主要原因:
其实 UE 选择自己实现模板库主要是历史遗留问题造成的, UE 发布初版的时候(1998年),当时的 STL 还很不成熟,跨平台表现更是糟糕,当时的 UE 自然不会选择使用 STL,而是改以实现自己的模板库代码,这些代码历经几代传承,也便沿用到了现在(UE4),同时 Tim 也表示目前的 STL 已经非常稳定,可以考虑在 UE 中逐步替换之前虚幻自己实现的模板库了.
回过头来我们再看看之前人们提出的各种 STL 的不足(或者不使用 STL 的优点),你会发现,其实这些论点都是基于特定的使用场景提出的,如果从更广阔的视角综合考虑的话,我们都能轻松的找到反驳这些观点的论据.
就拿上面那个 unordered_set_lite 来说,只要我们需要维护的元素越过了特定阈值,其性能就会落后于 unordered_set ,诚然,我们似乎可以往通用性的方向继续完善 unordered_set_lite,但是最后你会发现,自己只是又造了一个 unordered_set 的轮子,而且大概率还是一个更加简陋的轮子,但同时我们也不能否认,如果需要维护的元素确实不多的话,使用 unordered_set_lite 是要优于使用 unordered_set 的,总的来说, unordered_set_lite 与 unordered_set 的取舍仍然是一个工程问题,而工程问题,似乎永远都是一个权衡的游戏.
至此,我们便能理解上面那个迷幻问题了:为何人们对于是否使用 STL 的观点大相径庭了.
支持使用 STL 的人们基本是从更 generic 的角度出发的,而反对使用 STL 的人们基本是从更 specific 的角度出发的,两者看待问题的角度不同,得出的结论自然也就不同.
而所谓的 C++ 标准库的使用迷思,不过是不同角度看待 STL的产物罢了~
那么最后的问题来了,你看待 STL 目前是什么角度呢 ?
参考资料
- Why doesn’t UE utilize STL containers?
- Why do big projects like Unreal Engine write their own container classes?
- EASTL – Electronic Arts Standard Template Library
- Rocket and the STL
还没有评论,来说两句吧...