四.内存基本处理工具
4.1 概述
STL定义了五个全局函数作用在未初始化空间上。分别是construc(), destory(),uninitialized_copy(),uninitialized_fill()和uninitialized_fill_n(),其中前两个已经在博文《STL 空间配置器(一)》中进行了介绍,在该篇将介绍后三个函数以及uninitialized_copy与copy的区别。
4.2 uninitialized_copy
uninitialized_copy函数原型如下图所示:
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
uninitialized_copy(_InputIter __first, _InputIter __last, _ForwardIter __result)
{
return __uninitialized_copy(__first, __last, __result, __VALUE_TYPE(__result));
}
// 以下两个为uninitialized_copy的特化版本
inline char* uninitialized_copy(const char* __first, const char* __last, char* __result) {
// memmove可以正确的处理源区域与目标区域重叠,且源区域在目标区域之前的情况
memmove(__result, __first, __last - __first);
return __result + (__last - __first);
}
inline wchar_t* uninitialized_copy(const wchar_t* __first, const wchar_t* __last, wchar_t* __result)
{
memmove(__result, __first, sizeof(wchar_t) * (__last - __first));
return __result + (__last - __first);
}
uninitialized_copy定义了一个模板函数与两个特化版本,因为对于使用基础类型指针的情况,调用memove复制内存块是最为高效的。若是迭代器则使用__VALUE_TYPE萃取迭代器所指元素的类型,并调用__uninitialized_copy函数。该函数定义如下:
template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter __uninitialized_copy(_InputIter __first, _InputIter __last, _ForwardIter __result, _Tp*)
{
typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;
return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}
在该函数中通过萃取机__type_traits<_Tp>得知迭代器所指元素类型(通过__VALUE_TYPE获取)是否为pod类型,并将其作为参数传递给__uninitialized_copy_aux()
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last, _ForwardIter __result, __true_type)
{
return copy(__first, __last, __result);
}
template <class _InputIter, class _ForwardIter>
_ForwardIter __uninitialized_copy_aux(_InputIter __first, _InputIter __last, _ForwardIter __result, __false_type)
{
_ForwardIter __cur = __result;
__STL_TRY {
for ( ; __first != __last; ++__first, ++__cur)
_Construct(&*__cur, *__first);
return __cur;
}
__STL_UNWIND(_Destroy(__result, __cur));
}
根据是否是POD类型调用两个重载函数中的一个,若是POD类型则调用copy函数进行拷贝,若非POD类型则依次遍历[__first,__last)区间,使用其中对象作为参数调用拷贝构造函数,以此来在__result 到 __result + (__last - __first)之间构造对象。
4.3 uninitialized_copy 与 copy之间的区别
copy函数的具体细节会在STL基本算法相关的博文中介绍,这里只是简单的介绍一下,以说明两者之间的区别。
1. 对于目的区间是未初始化的内存区间只能使用uninitialized_copy(POD类型除外)
首先uninitialized_copy的目的区间是一块未初始化的内存区,因此对于非POD类型的拷贝必须调用拷贝构造函数来初始化这块区间, 而不可以调用copy函数进行拷贝,因为copy函数假设目的区间是已经被初始化的,copy中对于有非默认赋值运算符的类型是调用赋值运算符进行复制的,其写法为:
template<class InputIterator, class OutputIterator>
inline OutputIterator __copy(InputIterator first, InputIterator last, OutputIterator result, input_inerator_tag)
{
for(; first != last; ++result, ++first) {
*result = *first;
}
return result;
}
若区间 [ result ,result + last - first)未初始化,则调用result的赋值构造函数将会引发未定义错误。但对于POD类型则调用copy或是uninitialized_copy都是可以的,因为copy对于POD类型直接调用 memmove进行内存拷贝(当然要求以原始指针调用copy才会调用memmove),而uninitialized_copy对POD类型会调用copy函数,转而调用memmove。但对于某写只有默认运算的类型在未初始化的空间上直接调用copy仍是危险的,虽然调用的是memmove,但是却没有调用构造函数,如果构造函数中有某些必不可少的操作也会导致问题。
【注】:只有以原始指针为参数调用copy,且所指类型是由默认赋值运算符才可以调用memmove。
2.对于目的区间是已初始化的内存区只能使用copy (POD类型除外)
若对于已初始化的目的区间调用uninitialized_copy函数会导致再次调用拷贝构造函数,从而造成构造函数与析构函数的调用次数 不匹配,从而造成内存泄漏等问题(但POD类型除外,因为其不会调用拷贝构造函数,而是直接使用memmove进行内存拷贝),举例如下:
// 分别用于记录调用构造函数,拷贝构造函数,析构函数的次数
int callNum = 0, copyCallNum = 0, dCallNum = 0;
struct MyStruct {
MyStruct() {
num = new int[10];
callNum++;
}
~MyStruct() {
delete[] num;
dCallNum++;
}
MyStruct(MyStruct& value) {
num = new int[10];
memcpy(num, value.num, 4 * 10);
copyCallNum++;
}
int* num;
};
int main()
{
{ // 作用域
vector<MyStruct> vec1(3);
for (int i = 0; i < 3; i++)
for(int j = 0; j < 10; j++)
vec1[i].num[j] = 9;
vector<MyStruct> vec2(3);
uninitialized_copy(vec1.begin(), vec1.end(), vec2.begin());//调用了拷贝构造
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 10; j++) {
cout << vec2[i].num[j] << " ";
}
cout << endl;
}
}
cout << callNum << " " << copyCallNum << " " << dCallNum << endl; //输出:6(构造函数) 3(拷贝构造) 6(析构函数)
return 0;
}
观察输出结果可以发现构造函数与拷贝构造函数的调用总次数6 + 3 = 9,而析构函数只调用了6次,这导致了堆内存未被 释放,造成内存泄漏 。而将uninitialized_copy改为copy则发现刚好相等,因为调用的是赋值运算符。
4.4 其它两个内存基本处理函数
剩余两个函数的流程与uninitialized_copy函数类似,此处不再赘述,可以参考STL 源码剖析 一书。