您的位置:首页 > 其它

写应用程序的要求和库的要求是断然不同的--- 有感于STL insert_n

2009-09-22 10:58 423 查看
在跨平台的大计上, 在Symbian上你找不到STL,在Windows Mobile,Brew上也没有RArray。有显然的一点就是你需要重新写一个类似的容器来满足类似的需求,在这里强烈地感觉到写应用程序的要求和Library是断然不同的。

我们在std::Vector里面有一些常用的insert, resize等操作通常有一个共同的内部实现函数, 在MS STL里面我们看到的是_Insert_n, 在SGI-STL我们看到的是_M_insert_aux。我们通过代码来理解一下精妙之处。且看_Insert_n。

void _Insert_n(iterator _Where,
size_type _Count, const _Ty& _Val)
{	// insert _Count * _Val at _Where
#if _HAS_ITERATOR_DEBUGGING
if (_Where._Mycont != this
|| _Where._Myptr < _Myfirst || _Mylast < _Where._Myptr)
_DEBUG_ERROR("vector insert iterator outside range");
#endif /* _HAS_ITERATOR_DEBUGGING */
_Ty _Tmp = _Val;	// in case _Val is in sequence
size_type _Capacity = capacity();
if (_Count == 0)
;
else if (max_size() - size() < _Count)
_Xlen();	// result too long
else if (_Capacity < size() + _Count)
{	// not enough room, reallocate
_Capacity = max_size() - _Capacity / 2 < _Capacity
? 0 : _Capacity + _Capacity / 2;	// try to grow by 50%
if (_Capacity < size() + _Count)
_Capacity = size() + _Count;
pointer _Newvec = this->_Alval.allocate(_Capacity);
pointer _Ptr = _Newvec;
_TRY_BEGIN
_Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),
_Newvec);	// copy prefix
_Ptr = _Ufill(_Ptr, _Count, _Tmp);	// add new stuff
_Umove(_VEC_ITER_BASE(_Where), _Mylast, _Ptr);	// copy suffix
_CATCH_ALL
_Destroy(_Newvec, _Ptr);
this->_Alval.deallocate(_Newvec, _Capacity);
_RERAISE;
_CATCH_END
_Count += size();
if (_Myfirst != 0)
{	// destroy and deallocate old array
_Destroy(_Myfirst, _Mylast);
this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);
}
#if _HAS_ITERATOR_DEBUGGING
this->_Orphan_all();
#endif /* _HAS_ITERATOR_DEBUGGING */
_Myend = _Newvec + _Capacity;
_Mylast = _Newvec + _Count;
_Myfirst = _Newvec;
}
else if ((size_type)(_Mylast - _VEC_ITER_BASE(_Where)) < _Count)
{	// new stuff spills off end
_Umove(_VEC_ITER_BASE(_Where), _Mylast,
_VEC_ITER_BASE(_Where) + _Count);	// copy suffix
_TRY_BEGIN
_Ufill(_Mylast, _Count - (_Mylast - _VEC_ITER_BASE(_Where)),
_Tmp);	// insert new stuff off end
_CATCH_ALL
_Destroy(_VEC_ITER_BASE(_Where) + _Count, _Mylast + _Count);
_RERAISE;
_CATCH_END
_Mylast += _Count;
#if _HAS_ITERATOR_DEBUGGING
_Orphan_range(_Where._Myptr, _Mylast);
#endif /* _HAS_ITERATOR_DEBUGGING */
fill(_VEC_ITER_BASE(_Where), _Mylast - _Count,
_Tmp);	// insert up to old end
}
else
{	// new stuff can all be assigned
pointer _Oldend = _Mylast;
_Mylast = _Umove(_Oldend - _Count, _Oldend,
_Mylast);	// copy suffix
#if _HAS_ITERATOR_DEBUGGING
_Orphan_range(_Where._Myptr, _Mylast);
#endif /* _HAS_ITERATOR_DEBUGGING */
_STDEXT _Unchecked_move_backward(_VEC_ITER_BASE(_Where), _Oldend - _Count,
_Oldend);	// copy hole
fill(_VEC_ITER_BASE(_Where), _VEC_ITER_BASE(_Where) + _Count,
_Tmp);	// insert into hole
}
}


我们可以关注的是 _Ty _Tmp = _Val; // in case _Val is in sequence
这句毫不起眼的语句, 我们一般来说在特定的位置插入一个新的元素的流程是:如果capacity充足,把该位置后面的内容整体向后拷贝一位,然后把对应位置的内容用_Val来拷贝。这样的流程在很多library都如此, 但是问题来了,如果这里_Val原本就在这个这个vector里面,那前面一次的拷贝移动就有可能把_Val的内容写坏!

所以在这里需要吧_Val的内容首先拷贝出来, 然后才可以在移动之后再填入内容:

_Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),
_Newvec);	// copy prefix
_Ptr = _Ufill(_Ptr, _Count, _Tmp);	// add new stuff


可能这里的写法源自于bug的发现和修改,但是其健壮程度还是让我们动容,自叹不如的。类似的,我们看到了SGI-STL里面的_M_insert_aux函数实现:

template <class _Tp, class _Alloc>
void
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
if (_M_finish != _M_end_of_storage) {
construct(_M_finish, *(_M_finish - 1));
++_M_finish;
_Tp __x_copy = __x;
copy_backward(__position, _M_finish - 2, _M_finish - 1);
*__position = __x_copy;
}
else {
const size_type __old_size = size();
const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
iterator __new_start = _M_allocate(__len);
iterator __new_finish = __new_start;
__STL_TRY {
__new_finish = uninitialized_copy(_M_start, __position, __new_start);
construct(__new_finish, __x);
++__new_finish;
__new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
}
__STL_UNWIND((destroy(__new_start,__new_finish),
_M_deallocate(__new_start,__len)));
destroy(begin(), end());
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
_M_start = __new_start;
_M_finish = __new_finish;
_M_end_of_storage = __new_start + __len;
}
}


我们也看到了

_Tp __x_copy = __x;
copy_backward(__position, _M_finish - 2, _M_finish - 1);
*__position = __x_copy;


类似的实现。 可以看到这里的写法真是如出一辙。 原来以为自己写的代码到可以运行就已经可以了, 不错了。但是实际上合优秀的代码的差距还是如此之巨大,自己也深受感动啊。当然类似的感叹也发生在著名的memmove上面,我们知道,在这个函数出现之前有一个memcpy。但是在source和dest有overlap的情况下,这个memcpy就不能正常工作的, 原因在什么地方呢?类似的strycpy的实现我们看到过很多都是 *p++ = *q++; 而没有把交集位置的移动考虑进去, 所以在memmove中做出了修正, 这里就有一个类似的实现:

void* ROKEN_LIB_FUNCTION
memmove(void *s1, const void *s2, size_t n)
{
char *s=(char*)s2, *d=(char*)s1;
if(d > s){
s+=n-1;
d+=n-1;
while(n){
*d--=*s--;
n--;
}
}else if(d < s)
while(n){
*d++=*s++;
n--;
}
return s1;
}


这里会比较一下s1和s2也就是source和destination的首地址,根据地址的不同继而选择拷贝的顺序,因为我们知道总有一种方向的按位拷贝是安全的!希望把这些优秀的写法记录下来, 让自己的代码也更上一个台阶 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐