几个使用STL遇到的问题

mathe 2008-02-21 09:17:48
我在过去使用STL过程中,遇到过一些问题。
i)记得过去使用STL,查找VC6里面关于list::erase函数的说明,说是调用了这个函数以后,所有关于list的iterator就不能再使用了。
这个一直让我很困惑,如果这样不是只要一删除list里面的某个元素,所有原先的iterator就都不能用了,这个实在很不方便(对效率影响太大了)。
不过干才重新查了一下VC2005里面关于list::erase的说明,发现这个问题消失了:
Return Value
A bidirectional iterator that designates the first element remaining beyond any elements removed, or a pointer to the end of the list if no such element exists.
Remarks
No reallocation occurs, so iterators and references become invalid only for the erased elements.
看来这个应该是过去VC6对STL实现有问题,是不是C++的标准中已经规定了list::erase不能对已经存在的iterators有影响所以现在VC2005里面的实现发生变化了?
前一段时间我参与的一个项目中我就因为印象中VC的STL有这个问题而否决了用list<>。
ii)hash表我觉得是非常好用的一个数据结构,可是好像标准STL里面不支持,不知道有没有添加到最新的C++标准。不知道C++标准弃用hash表的理由是什么?
iii)这个是关于allocator的问题,我现在的项目中会牵涉到一种像多维堆栈似的内存管理模式,我们也可以看成程序分成多个阶段,各个阶段形成一个树状结果,任何时候,我们都处在树结构的一个节点上,这时,我们能够访问当前节点以及这个节点的所有祖先节点(所以像一个堆栈结构)。而每一个节点,都维护了一个线性的内存管理结构(实际上就是为每个节点分配一个内存池,然后在退出这个节点(当然也推出了所有子节点)时释放这个节点的内存池)。主要的问题是由于可以同时管理当前节点和祖先节点,我们可能会在多个内存池中交替分配内存。而如果使用STL,那么很自然想到要自定义allocator类来加以实现,可是allocator类我们无法带上数据参数,从而很难实现内存池切换。其实我刚开始已经决定使用STL了,后来使用过程中又弃用了,所以只得重新自定义list,vector之类的类。后来又自定义了hashtable,hashset。不过由于感觉实现rbtree比较复杂,就没有实现。这个导致现在应该用
map,hashmap,bitset的地方也用hashtable和hashset了,感觉有点滥用hash表。
所以现在STL让我有点鸡肋的感觉,但是里面又有些东西的确直接使用挺方便的。
不知道你有什么好建议?
...全文
711 41 打赏 收藏 转发到动态 举报
写回复
用AI写文章
41 条回复
切换为时间正序
请发表友善的回复…
发表回复
Trws 2011-12-20
  • 打赏
  • 举报
回复
i)记得过去使用STL,查找VC6里面关于list::erase函数的说明,说是调用了这个函数以后,所有关于list的iterator就不能再使用了。
被删除的it确实是不能用了。但可以使用list.erase(it++);类似这样的语法来拿到下个迭代器。关于删除的问题大多还是由容器本来的性质决定的,序列型容器,只要删一个,全部迭代器都会失效,但关联型的,只有被删的一个失效。
ii)hash表我觉得是非常好用的一个数据结构,可是好像标准STL里面不支持,不知道有没有添加到最新的C++标准。不知道C++标准弃用hash表的理由是什么?
这个问题。。。难以回答啊。据说快有哈希表了吧。目前不在标准中的原因估计是因为要设计一个普适的哈希表的标准可能有些困难。用什么哈希函数呢,冲突时如何解决呢之类。
iii)这个是关于allocator的问题,我现在的项目中会牵涉到一种像多维堆栈似的内存管理模式,我们也可以看成程序分成多个阶段,各个阶段形成一个树状结果,任何时候,我们都处在树结构的一个节点上,这时,我们能够访问当前节点以及这个节点的所有祖先节点(所以像一个堆栈结构)。而每一个节点,都维护了一个线性的内存管理结构(实际上就是为每个节点分配一个内存池,然后在退出这个节点(当然也推出了所有子节点)时释放这个节点的内存池)。主要的问题是由于可以同时管理当前节点和祖先节点,我们可能会在多个内存池中交替分配内存。而如果使用STL,那么很自然想到要自定义allocator类来加以实现,可是allocator类我们无法带上数据参数,从而很难实现内存池切换。其实我刚开始已经决定使用STL了,后来使用过程中又弃用了,所以只得重新自定义list,vector之类的类。后来又自定义了hashtable,hashset。不过由于感觉实现rbtree比较复杂,就没有实现。这个导致现在应该用
map,hashmap,bitset的地方也用hashtable和hashset了,感觉有点滥用hash表。
所以现在STL让我有点鸡肋的感觉,但是里面又有些东西的确直接使用挺方便的。
不知道你有什么好建议?

关于这个问题。。还不是很明白你们的难点。如果说想要allocator类带数据参数是不是希望给容器传一个实例化过的allocator,并且这个allocator只在实例化时会申请一个空间,之后只在这有限的空间里分配?如果是的话,那么stl容器是支持的。 array_allocator就是类似这样的一个分配器。
mathe 2008-02-25
  • 打赏
  • 举报
回复
呵呵,你贴过来的内容看得我云里雾里,仔细辨别了很长时间才发现你的问题。

首先,我们需要从STL标准的模板类allocator<T>派生类my_allocator<T>,这个派生类的构造函数可以使用一个参数,这个输入参数就是内存池
Vitin上面简单的用数字来表示了,真正情况可能类似:

template <T>
class my_allocator:public allocator<T>{
MemPool *local_pool;
public:
my_allocator(MemPool *p):local_pool(p){}
T* allocate(size_type n, const void *v=0){///重载allocator::allocate函数来分配内存
return static_cast<T *>local_pool->alloc(n);
}
///...///后面还要重载一些其他函数,如deallocator等
};
MemPool *p1,*p2;
p1=CreatePool(...);
p2=CreatePool(...);
my_allocator<T> alloc_1(p1);///申明一个allocator使用内存池p1
my_allocator<T> alloc_2(p2);///申明一个allocator使用内存池p2

vector<T, my_allocator<T> > vct(alloc_1);///这个vector将使用内存池p1来动态管理内存
deque<T, my_allocator<T> > dq(alloc_2);///这个deque将使用内存池p2来动态管理内存。

实际使用中,可能上面的代码会有些问题,这个同内存池使用方式有关系,实际用法可能是

///...
MemPool *p1,*p2;
p1=CreatePool(...);
p2=CreatePool(...);
my_allocator<T> alloc_1(p1);///申明一个allocator使用内存池p1
my_allocator<T> alloc_2(p2);///申明一个allocator使用内存池p2
typedef vector<T, my_allocator<T> > My_Vector;
typedef deque<T, my_allocator<T> > My_Deque;
My_Vector *vct = new(p1->alloc(sizeof(My_Vector))) My_Vector(alloc_1);///这个vector将使用内存池p1来动态管理内存
My_Deque *dq = new(p2->alloc(sizeof(My_Deque))) My_Deque(alloc_2);///这个deque将使用内存池p2来动态管理内存。
vct->push_back(T(...));///使用p1中的内存
dq->push_back(T(...));///使用p2中内存
///....
DeletePool(p1);///删除所有p1分配的内存,所以vector的析构函数可以不调用了,是不是这里同C++通常编程模式又有点不相符了:)
DeletePool(p2);///删除所有p2分配的内存,所以deque的析构函数可以不调用了

mathe 2008-02-25
  • 打赏
  • 举报
回复
呵呵,你贴过来的内容看得我云里雾里,仔细辨别了很长时间才发现你的问题。

首先,我们需要从STL标准的模板类allocator<T>派生类my_allocator<T>,这个派生类的构造函数可以使用一个参数,这个输入参数就是内存池
Vitin上面简单的用数字来表示了,真正情况可能类似:

template <T>
class my_allocator:public allocator<T>{
MemPool *local_pool;
public:
my_allocator(MemPool *p):local_pool(p){}
T* allocate(size_type n, const void *v=0){///重载allocator::allocate函数来分配内存
return static_cast<T *>local_pool->alloc(n);
}
...///后面还要重载一些其他函数,如deallocator等
};
MemPool *p1,*p2;
p1=CreatePool(...);
p2=CreatePool(...);
my_allocator<T> alloc_1(p1);///申明一个allocator使用内存池p1
my_allocator<T> alloc_2(p2);///申明一个allocator使用内存池p2

vector<T, my_allocator<T> > vct(alloc_1);///这个vector将使用内存池p1来动态管理内存
deque<T, my_allocator<T> > dq(alloc_2);///这个deque将使用内存池p2来动态管理内存。

实际使用中,可能上面的代码会有些问题,这个同内存池使用方式有关系,实际用法可能是

...
MemPool *p1,*p2;
p1=CreatePool(...);
p2=CreatePool(...);
my_allocator<T> alloc_1(p1);///申明一个allocator使用内存池p1
my_allocator<T> alloc_2(p2);///申明一个allocator使用内存池p2
typedef vector<T, my_allocator<T> > My_Vector;
typedef deque<T, my_allocator<T> > My_Deque;
My_Vector *vct = new(p1->alloc(sizeof(My_Vector))) My_Vector(alloc_1);///这个vector将使用内存池p1来动态管理内存
My_Deque *dq = new(p2->alloc(sizeof(My_Deque))) My_Deque(alloc_2);///这个deque将使用内存池p2来动态管理内存。
vct->push_back(T(...));///使用p1中的内存
dq->push_back(T(...));///使用p2中内存
....
DeletePool(p1);///删除所有p1分配的内存,所以vector的析构函数可以不调用了,是不是这里同C++通常编程模式又有点不相符了:)
DeletePool(p2);///删除所有p2分配的内存,所以deque的析构函数可以不调用了


[/code]
visame 2008-02-25
  • 打赏
  • 举报
回复
http://topic.csdn.net/u/20080221/09/1779a43c-96b6-443a-969c-d7c66ebe3472.html
下面这个关于内存池的能不能给个例子阿。看不太明白。
补充一下,对于第iii)点,可以使用以下方法:

C/C++ code


vector<T, my_allocator<T>> v1(my_allocator<T>(1)); // 使用内存池#1
vector<T, my_allocator<T>> v2(my_allocator<T>(2)); // 使用内存池#2
vector<T, my_allocator<T>> v3(v2); // 仍使用内存池#2
// my_allocator未必需要是模版类,可根据需要自行决定


也可以这样做:

C/C++ code


my_allocator<T> alloc_1(1); // 使用内存池#1;未必需要构造函数,也可以用set函数设定
my_allocator<T> alloc_2(2); // 使用内存池#2
....
vector<T, my_allocator<T>> vct(alloc_1);
deque<T, my_allocator<T>> dq(alloc_2);
list<T, my_allocator<T>> lst(alloc_1);
// 需要注意的是,每个容器对象内使用各自的Allocator对象,而非共享构造函数输入的对象
// 实现上,STL容器一般是调用Allocator类的拷贝构造函数;因此,必要时请定义my_allocator<T>的拷贝构造函数
mathe 2008-02-25
  • 打赏
  • 举报
回复
也就是说2003的C++标准可以满足我的要求了。等会再看看我Linux新版本gcc里面STL函数是否使用了Allocator作为参数.
现在Linux下面STL没有文档还是挺不好使用了,让人使用时没有把握。
mathe 2008-02-25
  • 打赏
  • 举报
回复
的确是的,如果不调用析构函数,某些情况可能会出现一些潜在的问题。
所以除非你对你底层非常了解(甚至还包括编译器可能做的行为等),最好还是调用一下析构函数。
主要目的不是为了释放内存给内存池,而是为了析构数据成员。
甚至有可能实际过程中我们会定义my_allocator<T>::deallocator为空函数(也就是实际不释放内存),
但是这时候调用析构函数也是有必要的
Vitin 2008-02-25
  • 打赏
  • 举报
回复
更正一下,上述vct->~My_Vector()是伪码。根据之前的typedef语句,应该调用~vector()。
Vitin 2008-02-25
  • 打赏
  • 举报
回复
mathe提供了代码实例,visame可以看一下。

补充一下:
关于内存的释放,建议使用以下方式;它的效率低一些,但是更安全(依赖于STL的实现代码):

///....
vct->~My_Vector();
dq->~My_Deque();
///析构函数调用my_allocator<T>::deallocator将(T或T数组的)内存返还给内存池,同时也析构自身数据成员。
///这样做的原因是:
///1、容器类可能存在动态分配的数据成员(不是T或T数组,不采用my_allocator<T>::allocator分配);
///2、容器类的基类(如果有的话)或某些数据成员内部可能动态分配了(不来自内存池的)内存,那么不析构它们也会导致内存泄漏。
DeletePool(p1);
DeletePool(p2);
Vitin 2008-02-24
  • 打赏
  • 举报
回复
补充一下,对于第iii)点,可以使用以下方法:

vector<T, my_allocator<T> > v1(my_allocator<T>(1)); // 使用内存池#1
vector<T, my_allocator<T> > v2(my_allocator<T>(2)); // 使用内存池#2
vector<T, my_allocator<T> > v3(v2); // 仍使用内存池#2
// my_allocator未必需要是模版类,可根据需要自行决定

也可以这样做:

my_allocator<T> alloc_1(1); // 使用内存池#1;未必需要构造函数,也可以用set函数设定
my_allocator<T> alloc_2(2); // 使用内存池#2
....
vector<T, my_allocator<T> > vct(alloc_1);
deque<T, my_allocator<T> > dq(alloc_2);
list<T, my_allocator<T> > lst(alloc_1);
// 需要注意的是,每个容器对象内使用各自的Allocator对象,而非共享构造函数输入的对象
// 实现上,STL容器一般是调用Allocator类的拷贝构造函数;因此,必要时请定义my_allocator<T>的拷贝构造函数
Vitin 2008-02-24
  • 打赏
  • 举报
回复

以下均摘自“ISO/IEC 14882:2003”C++ Standard

// i) [lib.list.modifiers]
iterator erase(iterator position);
iterator erase(iterator first, iterator last);
....
Effects: Invalidates only the iterators and references to the erased elements.
// 这回答了第一个问题。

// ii) 目前标准中不包含hash表,原因如9楼所说。

// iii)[lib.vector]
template <class T, class Allocator = allocator<T> >
class vector {
public:
...
explicit vector(const Allocator& = Allocator());
explicit vector(size_type n, const T& value = T(),const Allocator& = Allocator());
template <class InputIterator>
vector(InputIterator first, InputIterator last,const Allocator& = Allocator());
vector(const vector<T,Allocator>& x);
// 此外 [lib.deque]、[lib.list]、[lib.map]、[lib.multimap]、[lib.set]、[lib.multiset]中均有类似声明
// 即所有STL基本容器均可在构造时设置其使用的Allocator对象,因此可以定义带参数的Allocator构造函数区分不同内存池

// iv) 目前标准中不包含template struct less<const char*,const char*> 特化版本,估计是某些实现自行加入。


mathe 2008-02-24
  • 打赏
  • 举报
回复
顶一下,有没有看过最新C++标准的朋友,来确认一下
fish6344 2008-02-22
  • 打赏
  • 举报
回复
一般而言,C++标准不会限定纳入标准的容器(以及其它设施)的实现,但会严格规定其行为和性能规范(否则标准还有什么意义呢?)。看来就VS2005能够解决你的问题,足见你的需求在实际开发中具有普遍性。然而,纵观STL容器之构造器接口,都具有非常统一的行为规范(例如元素类型、数量、区间及缺省构造),这很可能是因标准之故!(当然,我是不想去看具体的ISO-C++98标准文本)
mathe 2008-02-22
  • 打赏
  • 举报
回复
www.sgi.com/tech/stl/hash.html
------------------------------
这个同我的问题没有任何关系呀?
fish6344 2008-02-22
  • 打赏
  • 举报
回复
哦,没有!从《C++标准程序库》及《STL源码剖析》上列举的各容器来看,容器的构造函数都没有以配置器(如你所说的allocator)作为构造函数的参数。上述二书讨论的STL容器都是遵循C++标准的,即是说,C++标准并未规定容器的构造函数必须有参数以接受一个内存配置器实参!
jixingzhong 2008-02-22
  • 打赏
  • 举报
回复
www.sgi.com/tech/stl/hash.html
wuzhenyu 2008-02-22
  • 打赏
  • 举报
回复
复杂啊。。看不懂
mathe 2008-02-22
  • 打赏
  • 举报
回复
有可能吧。但是书本不代表标准。更加不用说上面只是使用了几个例子。本来同一个容器的构造函数就不是唯一的。遵循标准和标准不是一回事。
mathe 2008-02-22
  • 打赏
  • 举报
回复
可惜VS2005满足要求不能解决我的问题,因为我们是要跨平台的。
AngelDevil 2008-02-22
  • 打赏
  • 举报
回复
mark
life02 2008-02-22
  • 打赏
  • 举报
回复
学习中,期待自己
加载更多回复(21)

64,637

社区成员

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

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