STL 空间配置器(三)

四.内存基本处理工具

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 源码剖析 一书。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值