C++的STL_swap trick和现代C++的方法
在**《effective STL》**中:
Item 17:使用“the swap trick”来削减过剩的容量 Use “the swap trick” to trim excess
capacity 由于vector的复制构造函数只为被复制的vector分配它所需要的空间,故可以用如下的方式来削减vector
v中过剩的容量:vector(v).swap(v)
熟读《effective STL》的你一定听过STL_swap trick。这是一种及时释放内存的重要方法。
什么是STL_swap trick
我们都知道,std::vector的内存增长是随着元素的增加自动增加的,但是std::vector却没有相应的随着元素的减少自动释放内存的逻辑。因此,假设我们有下面的这样的类:
class CMyDataManger{
std::vector m_data;
//…
void Clear() {
m_data.clear();//这样写,m_data的内存是不会释放的,
// 假设data占用了大量的内存,那么我们就达不到释放内存的目的
}
};
在C99时代,如果要正确释放内存,我们需要这样写:
class CMyDataManger{
std::vector m_data;
//…
void Clear() {
std::vector().swap(m_data); // 把一个临时的空的vector和m_data的内存交换,
// 这样,在函数返回时,临时的vector析构,释放原来m_data的内存。
}
};
这个技巧在很长时间里都被C++社区津津乐道,在很多开源项目里也能看到这类技巧的实践。
实际上,这不是什么值得骄傲的事情,只能说C++ STL在很长时间没能满足开发者的相应诉求,只能通过一些隐晦的技巧进行实践。
现代C++的方法
终于,到了C++11,vector引入了shrink_to_fit()成员函数,通过这个函数向容器发起一个非约束性的请求,让容器的容量减少到当前大小,这个函数同样加入到了std::string和std::deque等其他容器类型。这才让STL_swap trick失去了实践价值。
在C++11以后的版本中,上述代码应该改为:
class CMyDataManger{
std::vector m_data;
//…
void Clear() {
m_data.clear();
m_data.shrink_to_fit();//释放内存
}
};
这样,代码可读性大大增加,代码的意图不释自明。
非约束性的请求
细心的你发现上面的“非约束性的请求”修饰词,那么,什么是“非约束性的请求”呢?
“非约束性的请求”就是指请求不一定被满足,而且我也不会告诉你请求是否满足,我具体怎么做,我自己决定。
这个词的解释看上去很有深奥,落实到代码里,其实就是说shrink_to_fit的返回值是void,shrink_to_fit根据内部的状态和策略自己决定是否执行内存释放,而且我是否执行对后续所有逻辑不会产生影响。
为什么要这么做呢,这也是出于性能考虑设计的。
例如,当前元素的占用空间比实际内存只少了几个字节,如果重新申请内存把现在的元素拷贝过去需要消耗大量性能的,这样做非常不划算,所以即使你让我shrink_to_fit,我也不会去做。但是对于当前实际使用空间远小于我已经申请的内存空间,那么还是会马上释放内存。
但是在日常逻辑开发时,大家不要写“非约束性的请求”的函数,而是在函数执行成功就返回true,执行失败就返回false,调用者不要忽略返回值,这样才能确保逻辑上的正确。
“非约束性的请求”的应用场景是“是否执行成功对后续所有逻辑不会产生影响”才能使用。
结语:
今天,我们一起复习了《effective STL》的一个知识点,《effective STL》是 2001年出版的Scott Meyers大师Effective三部曲之一,内容非常经典,但是20多年过去了,也有不少内容需要修订和进化。阅读经典著作,既要考虑当时特定的历史背景,也要汲取高超的技巧和思路,同时也要思考现代C++更好的实践,才能发挥出经典著作的现代实用价值。