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对象,并以dataFile初始化!这是声明了一个函数data, 返回值为list
(1) 第一个参数是dataFile,它的类型是istream_iterator
(2) 第二个参数没有名称。类型是指向不带参数的函数指针!
注:VC6上编译有错,()没有忽略,如声明为:list
如果按直觉意图声明对象,需要加上一个():
list
这是正确的方式,经测试VC2005编译通过可以按意图声明为对象。
更好的方法是避免使用匿名istream_iterator对象:
istream_iterator
istream_iterator
list
注:编译不通过也是有好处的,至少提醒了我们,这样声明是有问题的:)
测试代码:
//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
先说这个解决吧,用boost::shared_ptr, 基于引用计数的智能指针。
void doSomething()
{
typedef boost::shared_ptr
vector
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
//使用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转移回 pA1,pA2设置为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/ deque的earse只接受iterator删除,所以要先找出删除值所在的iterator位置。无remove方法。string可接iterator,也可以接受位置索引值。也无remove方法。
2) remove并不真正删除元素,而且是将后面的值覆盖到要删除的位置上,之后返回新的尾元素iterator。所以再使用erase的区间删除从新尾iterator到原尾end()的这段元素才算真正地删除了。
vector
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 );
注:list有remove方法,直接用最高效。
如果容器是一个标准关联容器(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_if和swap,要么自己写一个循环删除
第一种方式是先将不被删除的值复制到一个新的容器里,再跟要删除的做一次交换。问题是这样会造成很多不必要的复制。
Container
Container
remove_copy_if( c.begin(), c.end(), inserter( goodValue, goodValue.begin() ), badValue );
c.swap( goodValue );
第二种方法是自己实现一个循环删除,但也要少心:(以下是错误方法)
for( Container
i != c.end(); ++i ) {
if( badValue( *i ) )
c.erase( i );
}
以上代码看似无问题,其实会导致不确定行为。当容器的一个元素被删除时,指向该元素的所有迭代器将变得无效!所以当c.erase( i );时,i迭代器变得无效了,所以就不能再向后迭代其它元素,而循环还要依赖于这个值!
为了避免这个问题,需要在erase之前有一个迭代器指向c中的下一个元素。最简单的方法是使用后缀递增!(因为关联容器的earse返回的是 void )
for( Container
i != c.end(); ) { // 结束条件留空
//作用的是i,传给了erase, 但是erase执行前也递增了i, 这个概念很重要
if( badValue( *i ) ) c.erase( i ++);
else ++i;
}
(3) 如果要在循环的内部做某些(除删除对象之外的)操作:
对于关联容器好办,在上面的循环里插入即可。
对于标准容器,就有少少不一样,因为它的erase操作可以返回删除元素后的下一个元素的有效迭代器。
for( Container
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。