【讨论】STL的remove、remove_if算法存在难以觉查的bug

BlueDog 2010-08-19 12:27:45
我们知道remove、remove_if它都是将后面的不移除元素覆盖需要移除的元素,并返回新的逻辑终点
对于值概念的对象,这样的确没有什么问题,但是对于需要使用析构的对象,则会出现错误

例如下面的代码

struct Point
{
int x;
Point(int v):x(v){}
~Point()
{
printf("%d point delete!\n",x);
}

bool operator == (int value)
{
return x == value;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
typedef std::vector<Point>::iterator Iter;
std::vector<Point> vec;

for(int i=0;i<6;i++)
vec.push_back(Point(i));

printf("-------------------------------------\n");
Iter newend = std::remove(vec.begin(),vec.end(),4);
printf("-------------------------------------\n");
vec.erase(newend,vec.end());
printf("-------------------------------------\n");

return 0;
}


4号point并没有被析构,被析构的是5号point!!!

实际上解决这个问题并不难,它应该在remove时,需要用swap来将需要移除的元素交换到尾部

对了我用的是vs2005
...全文
473 17 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
云瑀 2010-08-19
  • 打赏
  • 举报
回复
vector中erase是真正删除了元素, 迭代器访问不到了。 algorithm中的remove只是简单的把要remove的元素移到了容器最后面,迭代器还是

可以访问到的。因为algorithm通过迭代器操作,不知道容器的内部结构,所以无法做到真正删除。
vector<int> array;
array.erase(remove(array.begin(),array.end(),6),array.end());
删除数组中所有元素等于6的元素

remove并不真正从容器中删除元素(容器大小并未改变),而是将每一个与value不相等的元素轮番赋值给first之后的空间,返回值FowardIterator 标示出重新整理后的最后元素的下一个位置。
lqbk3 2010-08-19
  • 打赏
  • 举报
回复
没bug,楼主那下面的东西去看吧……
估计就能转过弯来了……

#include<iostream>
#include<vector>
#include<algorithm>
#include<tchar.h>
using namespace std;
struct Point
{
static int cc;
int x;
const int uid;
Point(int v):x(v),uid(cc++){}
Point(const Point &rhs):x(rhs.x),uid(cc++){}
~Point()
{
printf("one point delete!,the unique id is %d,and the value is %d\n",uid,x);
}
Point operator =(const Point &rhs){x=rhs.x;return *this;}

bool operator == (int value)
{
return x == value;
}
};

int Point::cc=0;

int _tmain(int argc, _TCHAR* argv[])
{
typedef std::vector<Point>::iterator Iter;
std::vector<Point> vec;

for(int i=0;i<6;i++)
vec.push_back(Point(i));

printf("-------------------------------------\n");
Iter newend = std::remove(vec.begin(),vec.end(),4);
printf("-------------------------------------\n");
vec.erase(newend,vec.end());
printf("-------------------------------------\n");

return 0;
}
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
4号point是被5号覆盖了,没有必要交换4号和5号。
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
跟你真没脾气 这个是最基本的 如果这个都不考虑, 还怎么拿C++混饭吃?

看我12楼的回复。
BlueDog 2010-08-19
  • 打赏
  • 举报
回复
这个。。。谁讲在点子上就行

其实大家都没有完整的表述出我的问题,不过星星的回答启发了我

Point& operator=(const Point& right)
{
this->~Point(); //这一句好象一般都不会想到要写吧。。。
x = right.x;
return *this;
}
我想很多人在工程中不会考虑哪么全吧。。。
嘿嘿。别见怪啊
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
楼主也是看到星星就服气的那种人么?10楼与2楼是同一人。

楼主看我4楼的解释, remove完成后,newend到oldend之间的元素序列是不重要的,不关心的。
云瑀 2010-08-19
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 mstlq 的回复:]

设计一个类,首先就是要考虑拷贝构造函数和赋值操作符要不要重载……
说完……
不再解释了
[/Quote]
推10楼.....
另外可参考Effective C++
条款6,10,11,12
BlueDog 2010-08-19
  • 打赏
  • 举报
回复
嗯,mstlq讲的有道理,可以在赋值前先对本身做一次析构,改成如下代码就行了

struct Point
{
int x;


Point(int v):x(v)
{

}
~Point()
{
printf("%d point delete!\n",x);
}

Point& operator=(const Point& right)
{
this->~Point();
x = right.x;
return *this;
}

bool operator == (int value)
{
return x == value;
}
};


不过这样remove的语义是多创造了一个 5号point,虽然结果依然正确,总觉得有些问题。。。
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
标准库算法,事实上要求元素有“值语义”。如果某个类含有无“值语义”的成员变量,应该重载复制构造函数,operator=函数, 析构函数来保证这个类是有“值语义”的。
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 bluedog 的回复:]

另外如果这个对象还自已管理一个指针,也会出现内存泄露的问题


struct Point
{
int x;
int * p; //由对象的构造和析构来管理内存
...
};

p 因为没有析构函数的调用就会出现问题。所以我说实际上remove这类的STL函数仅对“值意义”的对象好用
其它的就要很小心了。
[/Quote]


那对于你这个类,下面的代码会不会有内存泄露呢?
Point pt1;
Point pt2;
pt1 = pt2;
mstlq 2010-08-19
  • 打赏
  • 举报
回复
设计一个类,首先就是要考虑拷贝构造函数和赋值操作符要不要重载……
说完……
不再解释了
BlueDog 2010-08-19
  • 打赏
  • 举报
回复
另外如果这个对象还自已管理一个指针,也会出现内存泄露的问题


struct Point
{
int x;
int * p; //由对象的构造和析构来管理内存
...
};

p 因为没有析构函数的调用就会出现问题。所以我说实际上remove这类的STL函数仅对“值意义”的对象好用
其它的就要很小心了。
healer_kx 2010-08-19
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 bluedog 的回复:]

呵呵,我等高手来吧。

假如我这里的对象析构涉及到锁对象的释放问题

你就会发现被删除的对象的锁没有被释放,而一个不需要erase的对象确释放了锁

涉及到句柄也是如此。。。
[/Quote]

vector可以处理的是可以copy的值,你说的锁,还有Socket,File,从面向对象都属于不可复制的对象,
所以你只要知道STL remove做什么就可以了,可不要把这种东西往你研究的那个上面套用。。。

否则,你想想,一个Socket实例,再复制出来一份,是什么东西呢?
失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
难道你忘了三原则了么? 难道在operator= 中 你会不将旧值保存的对象和句柄释放么?
BlueDog 2010-08-19
  • 打赏
  • 举报
回复
呵呵,我等高手来吧。

假如我这里的对象析构涉及到锁对象的释放问题

你就会发现被删除的对象的锁没有被释放,而一个不需要erase的对象确释放了锁

涉及到句柄也是如此。。。




失落的凡凡 2010-08-19
  • 打赏
  • 举报
回复
remove算法里用的是 operator= 将后面元素的覆盖符合规则的元素,而不是用swap。为什么这样呢,因为按照remove这个动作的意义,remove后形成了一个从begin到newend的序列,而newend到oldend之间的元素都是无效的。所以这一段中的内容是什么是不重要的,不关心的。

65,186

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧