Effective STL学习总结二(6-10)

Effective STL学习总结二(6-10

kejieleung

6条:当心C++编译器最烦人的分析机制

         C++编译的准则是尽可能解释为函数。由于STL使用了模板,加上类型众多的interator,很容易做成错觉。

先对一些函数声明作解释:

int f1(double);   //声明一个参数为double的函数

int f2(double (d)) //()会忽略

{

         return 0;

}

 

int f3(double (*fn)()); //声明一个参数为函数指针fn的函数

int f4(double (*fn)())

{

         return 0;

}

 

int f5(double fn()); //同上,隐式指针函数

int f6(double ());

int f7(double fn())

{

         return 0;

}

 

再看看以下声明():

         ifstream dataFile("ints.data");

         list data( istream_iterator( dataFile), istream_iterator() );

 

         并是在声明一个data对象,并以dataFile初始化!这是声明了一个函数data, 返回值为list, 有两个参数:

(1)     第一个参数是dataFile,它的类型是istream_iterator其中()会忽略!

(2)     第二个参数没有名称。类型是指向不带参数的函数指针!

注:VC6上编译有错,()没有忽略,如声明为:list data( istream_iterator dataFile, istream_iterator() );则没问题,应该是对标准支持不够。VC2005通过,显示为函数!!DEV-C++也编译不能过!)

 

         如果按直觉意图声明对象,需要加上一个():

         list data( ( istream_iterator( dataFile) ), istream_iterator() );

         这是正确的方式,经测试VC2005编译通过可以按意图声明为对象。

 

         更好的方法是避免使用匿名istream_iterator对象:

         istream_iterator dataBegin( dataFile );

         istream_iterator dataEnd;

         list data( dataBegin, dataEnd );

 

注:编译不通过也是有好处的,至少提醒了我们,这样声明是有问题的:)

测试代码:

//VC2005

int _tmain(int argc, _TCHAR* argv[])

{

       ifstream dataFile1("ints.dat");

       //测试OK

       list<int> data1( ( istream_iterator<int> (dataFile1) ), istream_iterator<int>() );

       copy( data1.begin(), data1.end(), ostream_iterator<int>(cout, "/n") );

 

       //更好的方式

       ifstream dataFile2("ints.dat");

       istream_iterator<int> dataBegin( dataFile2 );

       istream_iterator<int> dataEnd;

       list<int> data2( dataBegin, dataEnd );

       copy( data2.begin(), data2.end(), ostream_iterator<int>(cout, "/n") );

       return 0;

}

 

7条:如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete

         如果是通过vector方式的vector,需要自己delete,但是这样delete是可以,但是不是异常安全的。即是说,如果在加入或者删除时有异常抛出,那么就会造成资源泄漏!!

         先说这个解决吧,用boost::shared_ptr, 基于引用计数的智能指针。

         void doSomething()

         {

                   typedef boost::shared_ptr SPW;

                   vector vwp;

                   for( int i=0; i

                            vwp.push_back( SPW( new Widget ) );

         }

         安全又快捷,只是要先装上boost库!

         千万要注意,不要以为auto_ptr会解决问题,而实际上是更多问题。因为auto_ptr没有引用计数,使用了一种权限转移的方法,同一时间只有一个对象的拥者

         另外如果想使用for_each函数方式删除容器的指针对象,需要自己写一个处理删除的函数对象

       template< typename T >

     //为什么要继承这个?以后再说

       struct Delete_Object: public unary_function< const T*, void>    {

              void operator () (const T* ptr ) const

              {

                     delete ptr;

              }

       };

         现在可以这样写:

         void doSomething()

         {

                  

                   for_each( vwp.begin(), vwp.end(),Delete_Object() );

         }

也可以以更简洁的方式,不用指定类型Widget使得实现类型安全:

       struct Delete_Object{

              template< typename T > //将模板声明放在这里

              void operator () (const T* ptr ) const

              {

                     delete ptr;

              }

       };

    那么删除时:

         void doSomething()

         {

                   deque dssp;

                   //使用for_each好处就是不用自己写循环删除

                   for_each(dssp.begin(),dssp.end(),Delete_Object () );   

         }

    上面for_each等价于:

    for( deque< SpecialString*>::iterator i = dssp.begin(); i != dssp.end(); ++i )

              delete *i;

 

8条:切忽创建包含auto_ptr的容器对象

         基于vector< auto_ptr<A> > vcsA; 的对象声明应该要禁止,不应该被编译通过。原因上一条已经有涉及,因为auto_ptr没有引用计数,使用了一种权限转移的方法,同一时间只有一个对象的拥者。看看以下形式:

       auto_ptr<A> pA1( new A);   // pA1 指向一个A对象

       auto_ptr<A> pA2( pA1 );     // pA2 指向 pA1 的对象,pA1 设置为NULL

A对象的权限从pA1转移到pA2

         如果有: pA1 = pA2 // 权限从pA2转移回 pA1pA2设置为NULL!

         解决方法是用boost::shared_ptr,基于引用计数就没有这个问题了。

 

9条:慎重选择删除元素的方法

         注意没有统一的方法对所有容器都适用的删除方式,不过的容器删除方法不一样。Effective STL里总结了以下几条经验:

(1)     要删除容器中有特定值的所有对象

         如果容器是vector, string, deque, 使用earse-remove的方式

                   c.earse( remove ( c.begin(), c.end(), XXX ), c.end() );

 

         注:1) vector/ dequeearse只接受iterator删除,所以要先找出删除值所在的iterator位置。无remove方法。string可接iterator,也可以接受位置索引值。也无remove方法。

                   2) remove并不真正删除元素,而且是将后面的值覆盖到要删除的位置上,之后返回新的尾元素iterator。所以再使用erase的区间删除从新尾iterator到原尾end()的这段元素才算真正地删除了。

       vector v;   // 建立一个vector 1-10填充它

       v.reserve(10); 

       for (int i = 1; i <= 10; ++i) {

             v.push_back(i);

       }

       cout << v.size();   // 打印10

       v[3] = v[5] = v[9] = 99;  // 设置3个元素为99

       remove(v.begin(), v.end(), 99); // 删除所有等于99的元素

       cout << v.size();   // 仍然是10

         (可参考http://hi.baidu.com/elliott_hdu/blog/item/343efe2911853dfb99250afc.html

        

         如果是list, 则使用list::remove

                   c.remove( XXX );

 

         注:listremove方法,直接用最高效。

 

         如果容器是一个标准关联容器(set, multiset, map, multimap ),则使用它的earse成员函数

                   c.erase( XXX ); //高效直接

         注:不能使用remove,也没有这个成员函数。

 

(2)     如果要删除容器中满足特定判别式的所有对象

         如果容器是vector, string, deque, 使用earse-remove_if的方式

                  有一判别函数: bool badValue( int ); 根据这个函数来判断是否要删除一个值。

                   c.earse( remove_if ( c.begin(), c.end(),badValue ), c.end() );

 

         如果是list, 则使用list::remove_if

                   c.remove( badValue );

        

         如果容器是一个标准关联容器(set, multiset, map, multimap ),要么使用remove_copy_ifswap,要么自己写一个循环删除

         第一种方式是先将不被删除的值复制到一个新的容器里,再跟要删除的做一次交换。问题是这样会造成很多不必要的复制。

         Container c;

         Container goodValue;

         remove_copy_if( c.begin(), c.end(), inserter( goodValue, goodValue.begin() ), badValue );

         c.swap( goodValue );

 

         第二种方法是自己实现一个循环删除,但也要少心:(以下是错误方法)

         for( Container::iterator it = c.begin();

                   i != c.end(); ++i ) {

                            if( badValue( *i ) )

                                     c.erase( i );

         }

         以上代码看似无问题,其实会导致不确定行为。当容器的一个元素被删除时,指向该元素的所有迭代器将变得无效!所以当c.erase( i );时,i迭代器变得无效了,所以就不能再向后迭代其它元素,而循环还要依赖于这个值!

         为了避免这个问题,需要在erase之前有一个迭代器指向c中的下一个元素。最简单的方法是使用后缀递增!(因为关联容器的earse返回的是 void

         for( Container::iterator it = c.begin();

                   i != c.end(); ) { // 结束条件留空

                   //作用的是i,传给了erase, 但是erase执行前也递增了i, 这个概念很重要

                   if( badValue( *i ) ) c.erase( i ++);     

                   else ++i;

         }

 

(3)     如果要在循环的内部做某些(除删除对象之外的)操作:

对于关联容器好办,在上面的循环里插入即可。     

         对于标准容器,就有少少不一样,因为它的erase操作可以返回删除元素后的下一个元素的有效迭代器

         for( Container::iterator it = c.begin();

                   i != c.end(); ) { // 结束条件留空

                   if( badValue( *i ) ) {

                            //做其它事

                            i = c.erase( i ); 

                   else ++i;

         }

 

 

10条:了解分配子(allocator)的约定和限制

         这章主要讲自己实现分配子的相关注意项,对分配子更细详了解可参考:http://blog.csdn.net/fuzj/archive/2006/10/29/1355894.aspx

 

你可能感兴趣的:(C/C++)