在跨平台的大計上, 在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,<br />size_type _Count, const _Ty& _Val)<br />{// insert _Count * _Val at _Where<br /> #if _HAS_ITERATOR_DEBUGGING<br />if (_Where._Mycont != this<br />|| _Where._Myptr < _Myfirst || _Mylast < _Where._Myptr)<br />_DEBUG_ERROR("vector insert iterator outside range");<br /> #endif /* _HAS_ITERATOR_DEBUGGING */<br />_Ty _Tmp = _Val;// in case _Val is in sequence<br />size_type _Capacity = capacity();<br />if (_Count == 0)<br />;<br />else if (max_size() - size() < _Count)<br />_Xlen();// result too long<br />else if (_Capacity < size() + _Count)<br />{// not enough room, reallocate<br />_Capacity = max_size() - _Capacity / 2 < _Capacity<br />? 0 : _Capacity + _Capacity / 2;// try to grow by 50%<br />if (_Capacity < size() + _Count)<br />_Capacity = size() + _Count;<br />pointer _Newvec = this->_Alval.allocate(_Capacity);<br />pointer _Ptr = _Newvec;<br />_TRY_BEGIN<br />_Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),<br />_Newvec);// copy prefix<br />_Ptr = _Ufill(_Ptr, _Count, _Tmp);// add new stuff<br />_Umove(_VEC_ITER_BASE(_Where), _Mylast, _Ptr);// copy suffix<br />_CATCH_ALL<br />_Destroy(_Newvec, _Ptr);<br />this->_Alval.deallocate(_Newvec, _Capacity);<br />_RERAISE;<br />_CATCH_END<br />_Count += size();<br />if (_Myfirst != 0)<br />{// destroy and deallocate old array<br />_Destroy(_Myfirst, _Mylast);<br />this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);<br />}<br /> #if _HAS_ITERATOR_DEBUGGING<br />this->_Orphan_all();<br /> #endif /* _HAS_ITERATOR_DEBUGGING */<br />_Myend = _Newvec + _Capacity;<br />_Mylast = _Newvec + _Count;<br />_Myfirst = _Newvec;<br />}<br />else if ((size_type)(_Mylast - _VEC_ITER_BASE(_Where)) < _Count)<br />{// new stuff spills off end<br />_Umove(_VEC_ITER_BASE(_Where), _Mylast,<br />_VEC_ITER_BASE(_Where) + _Count);// copy suffix<br />_TRY_BEGIN<br />_Ufill(_Mylast, _Count - (_Mylast - _VEC_ITER_BASE(_Where)),<br />_Tmp);// insert new stuff off end<br />_CATCH_ALL<br />_Destroy(_VEC_ITER_BASE(_Where) + _Count, _Mylast + _Count);<br />_RERAISE;<br />_CATCH_END<br />_Mylast += _Count;<br /> #if _HAS_ITERATOR_DEBUGGING<br />_Orphan_range(_Where._Myptr, _Mylast);<br /> #endif /* _HAS_ITERATOR_DEBUGGING */<br />fill(_VEC_ITER_BASE(_Where), _Mylast - _Count,<br />_Tmp);// insert up to old end<br />}<br />else<br />{// new stuff can all be assigned<br />pointer _Oldend = _Mylast;<br />_Mylast = _Umove(_Oldend - _Count, _Oldend,<br />_Mylast);// copy suffix<br /> #if _HAS_ITERATOR_DEBUGGING<br />_Orphan_range(_Where._Myptr, _Mylast);<br /> #endif /* _HAS_ITERATOR_DEBUGGING */<br />_STDEXT _Unchecked_move_backward(_VEC_ITER_BASE(_Where), _Oldend - _Count,<br />_Oldend);// copy hole<br />fill(_VEC_ITER_BASE(_Where), _VEC_ITER_BASE(_Where) + _Count,<br />_Tmp);// insert into hole<br />}<br />}
我們可以關注的是 _Ty _Tmp = _Val; // in case _Val is in sequence
這句毫不起眼的語句, 我們一般來說在特定的位置插入一個新的元素的流程是:如果capacity充足,把該位置後面的內容整體向後拷貝一位,然後把對應位置的內容用_Val來拷貝。這樣的流程在很多library都如此, 但是問題來了,如果這裡_Val原本就在這個這個vector裡面,那前面一次的拷貝移動就有可能把_Val的內容寫壞!
所以在這裡需要吧_Val的內容首先拷貝出來, 然後才可以在移動之後再填入內容:
_Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),<br />_Newvec);// copy prefix<br />_Ptr = _Ufill(_Ptr, _Count, _Tmp);// add new stuff
可能這裡的寫法源自於bug的發現和修改,但是其健壯程度還是讓我們動容,自歎不如的。類似的,我們看到了SGI-STL裡面的_M_insert_aux函數實現:
template <class _Tp, class _Alloc><br />void<br />vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)<br />{<br /> if (_M_finish != _M_end_of_storage) {<br /> construct(_M_finish, *(_M_finish - 1));<br /> ++_M_finish;<br /> _Tp __x_copy = __x;<br /> copy_backward(__position, _M_finish - 2, _M_finish - 1);<br /> *__position = __x_copy;<br /> }<br /> else {<br /> const size_type __old_size = size();<br /> const size_type __len = __old_size != 0 ? 2 * __old_size : 1;<br /> iterator __new_start = _M_allocate(__len);<br /> iterator __new_finish = __new_start;<br /> __STL_TRY {<br /> __new_finish = uninitialized_copy(_M_start, __position, __new_start);<br /> construct(__new_finish, __x);<br /> ++__new_finish;<br /> __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);<br /> }<br /> __STL_UNWIND((destroy(__new_start,__new_finish),<br /> _M_deallocate(__new_start,__len)));<br /> destroy(begin(), end());<br /> _M_deallocate(_M_start, _M_end_of_storage - _M_start);<br /> _M_start = __new_start;<br /> _M_finish = __new_finish;<br /> _M_end_of_storage = __new_start + __len;<br /> }<br />}
我們也看到了
_Tp __x_copy = __x;<br /> copy_backward(__position, _M_finish - 2, _M_finish - 1);<br /> *__position = __x_copy;
類似的實現。 可以看到這裡的寫法真是如出一轍。 原來以為自己寫的代碼到可以運行就已經可以了, 不錯了。但是實際上合優秀的代碼的差距還是如此之巨大,自己也深受感動啊。當然類似的感歎也發生在著名的memmove上面,我們知道,在這個函數出現之前有一個memcpy。但是在source和dest有overlap的情況下,這個memcpy就不能正常工作的, 原因在什麼地方呢?類似的strycpy的實現我們看到過很多都是 *p++ = *q++; 而沒有把交集位置的移動考慮進去, 所以在memmove中做出了修正, 這裡就有一個類似的實現:
void* ROKEN_LIB_FUNCTION<br />memmove(void *s1, const void *s2, size_t n)<br />{<br /> char *s=(char*)s2, *d=(char*)s1;<br /> if(d > s){<br /> s+=n-1;<br /> d+=n-1;<br /> while(n){<br /> *d--=*s--;<br /> n--;<br /> }<br /> }else if(d < s)<br /> while(n){<br /> *d++=*s++;<br /> n--;<br /> }<br /> return s1;<br />}
這裡會比較一下s1和s2也就是source和destination的首地址,根據地址的不同繼而選擇拷貝的順序,因為我們知道總有一種方向的按位拷貝是安全的!希望把這些優秀的寫法記錄下來, 讓自己的代碼也更上一個台階