写应用程序的要求和库的要求是断然不同的--- 有感于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。
我们可以关注的是 _Ty _Tmp = _Val; // in case _Val is in sequence
这句毫不起眼的语句, 我们一般来说在特定的位置插入一个新的元素的流程是:如果capacity充足,把该位置后面的内容整体向后拷贝一位,然后把对应位置的内容用_Val来拷贝。这样的流程在很多library都如此, 但是问题来了,如果这里_Val原本就在这个这个vector里面,那前面一次的拷贝移动就有可能把_Val的内容写坏!
所以在这里需要吧_Val的内容首先拷贝出来, 然后才可以在移动之后再填入内容:
可能这里的写法源自于bug的发现和修改,但是其健壮程度还是让我们动容,自叹不如的。类似的,我们看到了SGI-STL里面的_M_insert_aux函数实现:
我们也看到了
类似的实现。 可以看到这里的写法真是如出一辙。 原来以为自己写的代码到可以运行就已经可以了, 不错了。但是实际上合优秀的代码的差距还是如此之巨大,自己也深受感动啊。当然类似的感叹也发生在著名的memmove上面,我们知道,在这个函数出现之前有一个memcpy。但是在source和dest有overlap的情况下,这个memcpy就不能正常工作的, 原因在什么地方呢?类似的strycpy的实现我们看到过很多都是 *p++ = *q++; 而没有把交集位置的移动考虑进去, 所以在memmove中做出了修正, 这里就有一个类似的实现:
这里会比较一下s1和s2也就是source和destination的首地址,根据地址的不同继而选择拷贝的顺序,因为我们知道总有一种方向的按位拷贝是安全的!希望把这些优秀的写法记录下来, 让自己的代码也更上一个台阶 :)
我们在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的首地址,根据地址的不同继而选择拷贝的顺序,因为我们知道总有一种方向的按位拷贝是安全的!希望把这些优秀的写法记录下来, 让自己的代码也更上一个台阶 :)
相关文章推荐
- 《 BCG 原创 :系列 四》 为应用程序设置不同的风格
- 安装期间将应用程序重定向到不同web services
- OEM 应用程序要求的数据库权限超出了您当前具有的权限 解决方法
- 通过例子学习ABAP(四)--INSERT语句对于三种类型的内表不同效果
- 面试题: 已知一个含有n个不同元素的集合,要求打印其所有具有k个元素的子集(不允许有重复的)
- 不同应用程序域中访问数据!(反射)
- WAS ND集群中的HTTP内存会话复制对Java应用程序序列化编程的要求
- MySQL的Replaceinto与Insertinto.....onduplicatekeyupdate...真正的不同之处
- 关于不同应用程序存储IO类型的描述
- QT:用QSet储存自定义结构体的问题——QSet和STL的set是有本质区别的,QSet是基于哈希算法的,要求提供自定义==和qHash函数
- 编写一个windows应用程序,要求在窗口的用户区中绘制一个圆 ,当单击左键时,该圆放大;单击右键时,该圆缩小;按下ctrl键时的同时鼠标移动,则该圆会随鼠标移动而移动
- 运输公司对用户计算运费.路程越远每公里运费越低.每公里每吨货物的基本运费p = 3;用户需要输入货物重量w和距离s;根据距离的不同折扣d不同(具体见)下面的表格,要求根据用户输入的w和s,计算出总运费
- Service使用——不同应用程序间的绑定与信息传递
- 编写一个控制台应用程序,要求用户输入5个大写字母,如果用户输入的信息不满足要求,提示帮助信息并要求重新输入
- 1、2、2、3、4、5这六个数字,用java写一个main函数,打印出所有不同的排列, 如:512234、412345等.要求:"4"不能在第三位,"3"与"5"不能相连.
- JAVA--第十二周任务之1.编写一个应用程序,要求编写一个Panel的子类MyPanel,MyPanel中有一个文本框和一个按钮,要求MyPanel的实例作为其按钮的ActionEvent事件的监视
- spark不同模式下应用程序运行的日志存放位置
- java/应用程序,解析pdf的几种不同方式
- 要安排:3个A国人,3个B国人,3个C国人坐成一排,要求不能使连续的3个人是同一个国籍.求所有不同方案的总数?
- 通过例子学习ABAP(四)--INSERT语句对于三种类型的内表不同效果