七月 22, 2010

STL MAP LIST 遍历

Written by

 

for(iterator it = begin(); it != end(); ++it)

for(iterator it = begin(); it != end(); it++)

两种方式iterator遍历的次数是相同的,但在STL中效率不同,前++–返回引用,后++–返回一个临时对象,因为iterator是类模板,使用it++这种形式要返回一个无用的临时对象,而it++是函数重载,所以编译器无法对其进行优化,所以每遍历一个元素,你就创建并销毁了一个无用的临时对象。

不信的话你可以去看看C++的标准库,还有符合标准C++的教材,除了特殊需要和对内置类型外,基本都是使用++it来进行元素遍历的,不管是源代码还是教材中都是如此。

用户定义类型对操作符的重载应与内置操作符的行为相似,而且后自增/减往往是引用前自增/减来作为其实行的一个副本。

比如通常都是这种形式:

class foo
{
public:
foo& operator ++ (){return ++bar;}

foo operator ++ (int)
{
foo tmp = *this; // 创建临时对象 ★
++*this; // 调用前自增
return tmp; // 返回临时对象 ★
}

private:
int bar;
}

以上标★号的2个步骤有时是多余的,比如用STL中用iterator遍历容器,这样就造成了不必要的程序效率的损失。

这也是被一些从C移植到C++的程序员所频频忽视的细节,所以它们被称为从C带到C++中的编程恶习。

More Effective C++
Item 6:  Distinguish between prefix and postfix forms of increment and decrement operators.

对C++中的前/后自增/减操作符以及因C++的重载对他们所引发的效率问题有详细的讲解。以下是一部分内容:

If you’re the kind who worries about efficiency, you probably broke into a sweat when you first saw the postfix increment function. That function has to create a temporary object for its return value (see Item 19), and the implementation above also creates an explicit temporary object (oldValue) that has to be constructed and destructed. The prefix increment function has no such temporaries. This leads to the possibly startling conclusion that, for efficiency reasons alone, clients of UPInt should prefer prefix increment to postfix increment unless they really need the behavior of postfix increment. Let us be explicit about this.

When dealing with user-defined types, prefix increment should be used whenever possible, because it’s inherently more efficient. (注意这一句)

Let us make one more observation about the prefix and postfix increment operators. Except for their return values, they do the same thing: they increment a value. That is, they’re supposed to do the same thing. How can you be sure the behavior of postfix increment is consistent with that of prefix increment? What guarantee do you have that their implementations won’t diverge over time, possibly as a result of different programmers maintaining and enhancing them? Unless you’ve followed the design principle embodied by the code above, you have no such guarantee. That principle is that postfix increment and decrement should be implemented in terms of their prefix counterparts. You then need only maintain the prefix versions, because the postfix versions will automatically behave in a consistent fashion.

====

LIST

====

剖析STL容器遍历删除时诡异的erase(iter++)
———————————————————————
STL中结点类容器(如:list,hash_map)遍历时进行删除时,需要这样做:

for(list <int> ::iterator   iter   =   m_list.begin();   iter   !=   m_list.end();   )
{
if(需要删除)
{
m_list.erase(iter++);
}
else
++iter;
}

而不能这样:
for(list <int> ::iterator   iter   =   m_list.begin();   iter   !=   m_list.end();   ++iter)
{
if(需要删除)
{
m_list.erase(iter);
}
}

为什么呢?

以STL   list为例:

iterator的相关操作
_Self&   operator++()
{
this-> _M_incr();
return   *this;   
}

_Self   operator++(int)
{         _Self   __tmp   =   *this;
this-> _M_incr();
return   __tmp;                               //后缀++按照语意返回了++前的iterator,
}

void   _M_incr()   {   _M_node   =   _M_node-> _M_next;   }         //++的操作对于list结构来说,就是使iterator的_M_node指向下一个结点

iterator   erase(iterator   __position)
{       _List_node_base*   __next_node   =   __position._M_node-> _M_next;
_List_node_base*   __prev_node   =   __position._M_node-> _M_prev;
_Node*   __n   =   (_Node*)   __position._M_node;
__prev_node-> _M_next   =   __next_node;
__next_node-> _M_prev   =   __prev_node;     //上面的代码把删除结点__position的前后结点串起来,而移除_positoin
_STLP_STD::_Destroy(&__n-> _M_data);   //call   T::~T()
this-> _M_node.deallocate(__n,   1);                         //释放结点内存
return   iterator((_Node*)__next_node);

分析代码我们可以看出,erase会deallocate__position的_M_node,   在__position上再进行++是错误的。
所以不能在m_list.erase(iter)后,进行iter++.

哪为什么m_list.erase(iter++)可以呢?为什么不能用m_list.erase(++iter)?
参照operator++的代码我们可以找到答案,iter++返回了++之前的iter值,erase使用这个值能正确进行__position的前后结点的串接及删除正确的结点,而++iter返回的是++之后的iter,所以m_list.erase(++iter)串接不正确,iter-> _M_node也是失效的.

对于非结点类,如数组类的容器vector,string,deque,如果erase会返回下个有效的iterator,可以这样处理:
for(vector <int> ::iterator   iter   =   m_vector.begin();   iter   !=   m_vector.end();)
{
if(需要删除)
{
iter=m_vector.erase(iter);
}
else
++iter;
}

 

来自:http://hi.baidu.com/wither/blog/item/db40a5c3cf101d58b319a8c1.html

 

 

Category : C/C++VC++

Tags :

Comments

One Response

  1. 好久没来看博主的文章了,说的很有事理,点赞一下!

发表评论

电子邮件地址不会被公开。

Proudly powered by WordPress and Sweet Tech Theme