|
用户名:hanyu1980 笔名:hanyu1980 地区: 行业:其他 |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
设计改变中国!
异常的Rethrow分析
网上看了一篇类似的分析,仔细看了一下写的不错,现在把代码引用一下:
try
{
try
{
// 抛出一个异常对象
throw MyException("ex_obj1");
}
// 异常对象按值传递
catch(MyException e)
{
cout<<endl<<"捕获到一个MyException*类型的异常,名称为:"<<e.GetName()<<endl;
cout<<"下面重新抛出异常"<<endl<<endl;
// 异常对象重新被抛出
throw;
}
}
// 异常对象再次按值传递
catch(MyException e)
{
&nsp; cout<<endl<<"捕获到一个MyException*类型的异常,名称为:"<<e.GetName()<<endl;
}
这个地方异常作了四次构造,这四次构造分别称为obj1,obj2,obj3,obj4。逐一道来。
throw MyException("ex_obj1");看看这段代码。好像有一句话说,捕获的所有异常,永远不是异常对象本身。为什么这么说?MyException("ex_obj1")是构造了一个局部变量,但是这个局部变量被throw出去后会生成一个临时变量,我们所catch到的不是这个对象本身,而是这个临时对象。这个临时对象本身并没有什么,但却非常重要。因为rethrow是这个临时对象,而不是用值传递生成的新对象。所以用值传递的方式,不能将修改带到下一个catch块。后面两个catch就是obj3和obj4了,以后就不用想什么,对obj3和obj4所做的任何动作不会带到下一步,因为重新抛出的是obj2。
其实异常抛出很简单的,用引用传递就可以了。使用引用传递的好处就是每次catch都可以做点小动作。呵呵!
多人读单人写的MAP原理
今天,工作需要,领导教了一个多人读单人写的map的实现。
首要的是需要一个事件,一个临界区和一个map。
对于读操作需要Wait这个信号,如果成功了就可以读取map。
对于写操作比较复杂,可以分为如下几步:
2. 进入加临界区锁;这里是为了保证只有一个线程访问这段。
3. ResetEvent
4. 这里最好的办法是引用计数,目前在这里等了一会,等大家都出去。
5. 写操作。
6. 发信号。
7. 揭开临界区的锁
8. 离开。
今后我会做一个关于引用计数的实现。这并非本人所创,乃是今天学会的一个高招。
静态成员函数不能调用普通成员变量
这是个人人都知道的小问题,今天却折腾了心浮气躁的我20分钟。
就这么一点错误。静态成员函数是类共享的函数,而普通成员变量则是类所独有的,所以在静态成员函数中调用普通成员变量,最起码直接调用是不可以的。
这是一个小问题,但遇事要慢慢来,不要急。写这篇Blog最主要要提醒自己,凡事必有因,不要着急。
Traits技法
接触traits的技法已近三年多了,对它的理解也是一个循序渐进的过程,所以现在总结一下,以为后来者鉴。
从迭代器的概念来说,迭代器和容器从逻辑上是分开的,容器存储,迭代器访问。OK,对于模板来说,它知道元素的类型;对于迭代器这真是个伤心的问题。其实它并不知道元素的类型,所以没有办法了,这个时候就开始约定。使用Traits来推算出类型来。
SGI的实现是比较典型。例如:
typedef typename iterator_traits<_Iterator>::value_type value_type;
typedef typename iterator_traits<_Iterator>::reference reference;
typedef typename iterator_traits<_Iterator>::pointer pointer;
下面是iterator_traits的实现,从中可以看出从迭代器中萃取出类型。
template<typename _Iterator>
struct iterator_traits
{
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
在vector中这样定义,
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
typedef typename _Alloc::pointer pointer;
typedef __gnu_cxx::__normal_iterator<pointer, vector_type> iterator;
……
}
这是Allocator中的定义:
template<typename _Tp>
class allocator: public ___glibcxx_base_allocator<_Tp>
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Tp* pointer;
typedef const _Tp* const_pointer;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef _Tp value_type;
……
}
这是问题的根源,迭代器归根结底通过这里得到元素的类型。萃取的起点在于Allocator,从这里开始,我们开始获取了元素的型别。
这篇写得很不充分,欲知详情,请参见SGI的实现。
Effective STL之后记
终于开始写这篇后记了,从第一篇读后感的去年12月7号到现在,历时近40天。这40天时间在我看来虽然有点长,但是从效果来看,总的来说还是不错的。
40天前,我对容器有一些经验,对迭代器经验也有,但是不是很足,其他环节就比较差了;现在各方面都有了提高。现在对各个库也有了一点体验,我想下部书的目标,应该更加底层一点。
Effective STL之49-50
条款49:学习破解有关STL的编译器诊断信息
STL的编译错误提示信息是非常让人烦恼的事情,读懂编译器的提示非常重要。STL中有很多typedef,所以一个原则就是替代法,在这个上面,UltraEdit能够提供一些帮助。
其他还有一些总结在下面:
1. 对于vector和string,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。例如,如果你的源代码涉及vector<double>::iterator,编译器消息有时会提及double*指针。STLPort也许例外。
2. 提到back_insert_iterator、front_insert_iterator或insert_iterator的消息经常意味着你错误调用了back_inserter、front_inserter或inserter,一一对应,(back_inserter返回back_insert_iterator类型的对象,front_inserter返回front_insert_iterator类型的对象,而inserter返回insert_iterator类型的对象。)如果你没有调用这些函数,你(直接或间接)调用的一些函数做了。
3. 类似地,如果你得到的一条消息提及binder1st或binder2nd,你或许错误地使用了bind1st或bind2nd。(bind1st返回binder1st类型的对象,而bind2nd返回binder2nd类型的对象。)
4. 输出迭代器(例如ostream_iterator、ostreambuf_iterators,和从back_inserter、front_inserter和inserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。
5. 你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在<algorithm>中),也许是你试图给那算法用的类型出错了。例如,你可能传了错误种类的迭代器。要看看这样的用法错误是怎样报告的,通过把这段代码喂给你的编译器来启发(并愉快!)自己.
6. 你使用常见的STL组件比如vector、string或for_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件。正如条款48的解释,这问题会降临在长期以来都可以顺利编译而刚移植到新平台的代码。
如上所说,替代法是个好方法,不过有些提示是在过于要命,只能自己忍受。
条款50:让你自己熟悉有关STL的网站
SGI STL网站,http://www.sgi.com/tech/stl/。
STLport网站,http://www.stlport.org/。
Boost网站,http://www.boost.org/或者http://boost.sourceforge.net/
记住它们的地址,经常访问它们。
Effective STL之47-48
条款47:避免产生只写代码
这里的只写代码和文件的只写属性并不完全是一回事,意思是写的代码为了让别人去读,写出天书并不是一件很光荣的事情。我是这样觉得,特别是觉得很多连写,会觉得效率会提高的看法是可笑的。代码的长度和效率不是一个概念。STL本来就难以被理解,作为开发者有义务让不熟悉STL的人读懂代码。
条款48:总是#include适当的头文件
不需要太多内容,这就是全部:
l 几乎所有的容器都在同名的头文件里,比如,vector在<vector>中声明,list在<list>中声明等。例外的是<set>和<map>。<set>声明了set和multiset,<map>声明了map和multimap。
l 除了四个算法外,所有的算法都在<algorithm>中声明。例外的是accumulate(参见条款37)、inner_product、adjacent_difference和partial_sum。这些算法在<numeric>中声明。
l 特殊的迭代器,包括istream_iterators和istreambuf_iterators(参见条款29),在<iterator>中声明。
l 标准仿函数(比如less<T>)和仿函数适配器(比如not1、bind2nd)在<functional>中声明。
Effective STL之45-46
条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别
对无序区间的查找,我们当然是find,只有当需要统计拷贝的个数,count才有意义。如果对有序区间binary_search告诉你是否有。如果要知道位置,equal_range是一个好的选择。对有序需要的统计当然distance配合equal_range当然要比count的效率高。
1. 期望值是否存在?
(1) 无序区间:find
(2) 有序区间:binary_search
(3) set或者map:count
(4) multiset或者multimap:find
2. 期望值是否存在?如果有,第一个等于这个值的对象在哪里?
(1) 无序区间:find
(2) 有序区间:equal_range
(3) set或者map:count
(4) multiset或者multimap:find或者lower_bound
3. 第一个不在期望值之前的对象在哪里?
(1) 无序区间:find_if
(2) 有序区间:lower_bound
(3) set或者map:lower_bound
(4) multiset或者multimap:lower_bound
4. 第一个在期望值之后的对象在哪里?
(1) 无序区间:find_if
(2) 有序区间:upper_bound
(3) set或者map:upper_bound
(4) multiset或者multimap:upper_bound
5. 有多少对象等于期望值?
(1) 无序区间:count
(2) 有序区间:equal_range,然后distance
(3) set或者map:count
(4) multiset或者multimap:count
6. 等于期望值的所有对象在哪里?
(1) 无序区间:find(迭代)
(2) 有序区间:equal_range
(3) set或者map:equal_range
(4) multiset或者multimap:equal_range
当然,序列容器要用泛型算法,而关联容器用成员函数。
条款46:考虑使用函数对象代替函数作算法的参数
作为参数,如果比对器,自己的设计和STL的设计有什么区别?
如果是纯函数,一般来说,算是一次函数调用,而一般STL的调用operator()是内联函数,这样效率会高一些。所以要善用内联对象可能效率会更好一些。这和39有点冲突,需要博弈。
Effective STL之43-44
条款43:尽量用算法调用代替手写循环
Meyers阐述的优点:
1.效率:算法通常比程序员产生的循环更高效。
2.正确性:写循环时比调用算法更容易产生错误。
3.可维护性:算法通常使代码比相应的显式循环更干净、更直观。
手写循环,可能直接导致迭代器实效。这真是一个让人发疯的问题。可以通过一些仿函式来避免一些此类错误。所以这类问题有一个权衡。如果手写循环简单清晰,而且是原有STL没有提供的功能,否则还是选择STL的实现吧。
关于accumulate对副作用敏感,而for_each不敏感的区别在前面提到,如果有兴趣可以阅读代码。For_each和transform大不相同,这里也不赘述了。
条款44:尽量用成员函数代替同名的算法
这一条我知道的很早,最早用STL就知道这一原则。这里不用多说什么。两点:1.各种容器的成员函数有关于自身的优化。泛型只是最简单的线性操作。2.记得list吧,他的很多成员和泛型算法名字相同,其实大不相同。