一、问题描述。
map的key的小于操作符重载的实现不正确,导致在删除一个元素时候,CPU占用100%。
有问题的代码如下:
using namespace std;
class CUser
... {
public:
CUser(int iID,int iKey):miID(iID),miKey(iKey)...{};
~CUser()...{};
public:
int miID;
int miKey;
//小于操作符号重载有问题的实现
bool operator<(const CUser &rhs) const
...{
if(miID < rhs.miID)
...{
return true;
}
if(miKey < rhs.miKey)
...{
return true;
}
return false;
};
//小于操作符号重载正确的实现
/**//*
bool operator<(const CUser &rhs) const
{
if(miID != rhs.miID)
{
return miID < rhs.miID;
}
return miKey < rhs.miKey;
};
*/
} ;
typedef map < CUser, string > CUserMap;
typedef CUserMap::iterator CUMIterator;
int main( int argc, char * argv[])
... {
CUserMap cu;
cu[CUser(1,3)] = "1--3";//Node A
cu[CUser(3,1)] = "3--1";//Node B
//该map的结构为Head(end),A,B
cu.erase(CUser(3,1));//此处程序挂在这儿,CPU占用100%
cout << "hello"<<endl;
return 0;
}
二、原因分析
1、 这是插入A、B节点后map cu的树结构
2、 STL源码分析
(1)map的删除函数
size_type erase(
const
key_type
&
_Keyval)
//
_Keyval为B(3,1)
... { // erase and count all that match _Keyval
// equal_range返回_Keyval可以插入的第一个位置和最后一个位置
//[_Where.first, _Where.second),也就是元素=_Keyval的元素区间,
//此处正确的结果应该是[B,Head(end))
//但此次结果却为[B,A),导致执行_Distance时候,出现死循环
_Pairii _Where = equal_range(_Keyval);
size_type _Num = 0;
//_Distance计算_Where.second到_Where.first的距离,
//是通过++计算的
_Distance(_Where.first, _Where.second, _Num);
erase(_Where.first, _Where.second);
return (_Num);
}
... { // erase and count all that match _Keyval
// equal_range返回_Keyval可以插入的第一个位置和最后一个位置
//[_Where.first, _Where.second),也就是元素=_Keyval的元素区间,
//此处正确的结果应该是[B,Head(end))
//但此次结果却为[B,A),导致执行_Distance时候,出现死循环
_Pairii _Where = equal_range(_Keyval);
size_type _Num = 0;
//_Distance计算_Where.second到_Where.first的距离,
//是通过++计算的
_Distance(_Where.first, _Where.second, _Num);
erase(_Where.first, _Where.second);
return (_Num);
}
(2)
template
<
class
_BidIt,
class _Diff > inline
void _Distance2(_BidIt _First, _BidIt _Last, _Diff & _Off,
bidirectional_iterator_tag)
... { // add to _Off distance between bidirectional iterators (redundant)
// B++ == Head, Head ++ == Head
for (; _First != _Last; ++_First)
++_Off;
}
class _Diff > inline
void _Distance2(_BidIt _First, _BidIt _Last, _Diff & _Off,
bidirectional_iterator_tag)
... { // add to _Off distance between bidirectional iterators (redundant)
// B++ == Head, Head ++ == Head
for (; _First != _Last; ++_First)
++_Off;
}
(3)获取
_Where.second
的结果
_Nodeptr _Ubound(
const
key_type
&
_Keyval)
const
... { // find leftmost node greater than _Keyval
_Nodeptr _Pnode = _Root();
_Nodeptr _Wherenode = _Myhead; // end() if search fails
// _Keyval= B(3,1), _Pnode = A(1,3)
while (!_Isnil(_Pnode))
//此处comp调用了Cuser的<,B(3,1) < A(1,3) 应该是false,但是这里却返回true
if (this->comp(_Keyval, _Key(_Pnode)))
...{ // _Pnode greater than _Keyval, remember it
_Wherenode = _Pnode;
_Pnode = _Left(_Pnode); // descend left subtree
}
else
_Pnode = _Right(_Pnode); // descend right subtree
return (_Wherenode); // return best remembered candidate
}
... { // find leftmost node greater than _Keyval
_Nodeptr _Pnode = _Root();
_Nodeptr _Wherenode = _Myhead; // end() if search fails
// _Keyval= B(3,1), _Pnode = A(1,3)
while (!_Isnil(_Pnode))
//此处comp调用了Cuser的<,B(3,1) < A(1,3) 应该是false,但是这里却返回true
if (this->comp(_Keyval, _Key(_Pnode)))
...{ // _Pnode greater than _Keyval, remember it
_Wherenode = _Pnode;
_Pnode = _Left(_Pnode); // descend left subtree
}
else
_Pnode = _Right(_Pnode); // descend right subtree
return (_Wherenode); // return best remembered candidate
}
3、 根本原因
在插入的时候,首先插入的是A(1,3),在插入B时候,使用的比较操作是comp(A, B),但在删除的时候,用到是comp(B,A).
comp(A, B) => A(1,3) < B(3,1) 结果为true
comp(B, A) => B(3,1) < A(1,3) 结果为true
comp(A, B) == !( comp(B, A)),
即Cuser的< 不符合小于的定义
三、对策
1、在重载操作符时候,要使用其基本语意进行验证,看是否自相矛盾,例如在定义<时候,可以判断(A<B) != (B<A)是否为真。
2、操作符<的定义一般都是类似如下定义
bool
operator
<
(
const
CUser
&
rhs)
const
... {
if(miID != rhs.miID)
...{
return miID < rhs.miID;
}
return miKey < rhs.miKey;
} ;
... {
if(miID != rhs.miID)
...{
return miID < rhs.miID;
}
return miKey < rhs.miKey;
} ;