//////////////////////////////////////////////////////////////////
//auto_map: 析购时候对map对象的second区域字段释放内存
/////////////////////////////////////////////////////////////////
template <class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key, T> > >
class auto_map : public map<Key,T,Compare,Allocator>
{
public:
explicit auto_map(const Compare& comp = Compare(),
const Allocator& alloc = Allocator())
:map<Key,T,Compare,Allocator>(comp,alloc)
{
};
void clear()
{
fnFree();
map<Key,T,Compare,Allocator>::clear();
}
~auto_map()
{
fnFree();
};
private:
void fnFree()
{
// you must declare typename keyword here
typename map<Key,T,Compare,Allocator>::iterator it = map<Key,T,Compare,Allocator>::begin();
for(;it != map<Key,T,Compare,Allocator>::end();++it)
{
delete (*it).second;
}
}
};
converse 回复于:2006-09-28 11:22:18
要是second不是指针呢?
hellhell 回复于:2006-09-28 11:27:08
大概用functor对delete再包一层比较好
whyglinux 回复于:2006-09-28 21:33:10
STL 的设计中没有使用虚函数机制,这主要是从执行效率方面来考虑的。因此,对于 STL 中的容器类来说,它所有的成员函数,包括析构函数,都是非虚成员函数。而如果一个类的析构函数是非虚函数的话,就决定了不适合从这个类派生其它类型,因为如果这么做的话,通过基类指针删除指向的派生类对象时会造成不正确的析构。
如果要在容器类的基础上建立一个新的类型,用容器类声明一个新建类的数据成员是唯一正确的做法。千万不要把 C++ 标准库中的容器类作为基类使用。
wolf0403 回复于:2006-09-29 00:05:37
convers 那个签名啊,看一次一身冷汗……
引用:STL 的设计中没有使用虚函数机制,这主要是从执行效率方面来考虑的
晚生不以为然。STL 遵从 C++ 设计思想,一切以值语意为设计根本(与此相对的是 Java/C# 的引用语意),从一个类的复制构造函数的重要性就可以窥见端倪。STL 非常完整地继承并发扬了这个设计思路,容器元素只能是『值』——就算你放的是一个指针,它也只当作一个值来操作。
因此,使用 shared_ptr 才是王道。
wolf0403 回复于:2006-09-29 00:11:20
事实上,如果 STL 使用引用语意管理元素,效率可以得到进一步提升(想想 vector 重新分配时导致的全部对象复制)。
基于指针语意的容器可以参考 ptypes 系列:)
SeaKing911 回复于:2006-09-29 12:47:37
顶一个,立志要学好STL
whyglinux 回复于:2006-09-29 21:27:24
引用:原帖由 wolf0403 于 2006-9-29 00:05 发表
晚生不以为然。STL 遵从 C++ 设计思想,一切以值语意为设计根本(与此相对的是 Java/C# 的引用语意),从一个类的复制构造函数的重要性就可以窥见端倪。STL 非常完整 ...
容器类采用值语义还是引用语义只是决定了容器中元素的归属问题,不会影响容器的操作——值语义决定了在容器中操作的元素是对象的拷贝,对原来的对象没有影响;引用语义不产生拷贝,操作的是同一个对象。既然如此,那么值或引用语义也就不会影响到容器的接口界面,就更不用说界面的实现方式了,所以说这不是 STL 容器类不使用虚函数成员的原因所在。
这篇文章支持我前面的观点,供参考
http://www.codersource.net/published/view/326/stl_overview.aspx
题外话:“晚生不以为然”这句话在语意表达上不连贯。“晚生”当然是一种自谦,含虚心之意;之后的“不以为然”其意是表示不同意,但多含轻视意,结果到了这里语意陡转,谦虚之意荡然无存。这恐非本意。因此,可将“晚生不以为然”换为“晚生以为不然”,这就没有语病了。
引用:原帖由 wolf0403 于 2006-9-29 00:11 发表
事实上,如果 STL 使用引用语意管理元素,效率可以得到进一步提升(想想 vector 重新分配时导致的全部对象复制)。
基于指针语意的容器可以参考 ptypes 系列:)
如果真是你说的那样的话,STL 容器类会毫不犹豫地使用(至少是部分使用)引用语义。然而实际上 STL 一直基于值语义,这应该很能说明一些问题了。
需要清楚的是:引用是一种间接访问方式;另外,引用语义一般还需要进行引用计数,等等。这些“附加项”往往决定了引用语义容器无论在实现效率还是在执行效率的许多方面(比如遍历容器)都没有值语义容器高效。因此,情况都不是绝对的:想当然地认为引用语义一定比值语义高效是站不住脚的;不过,有的情况用引用语义会带来使用上的方便,有的情况也会带来效率上的提升。
实际中既存在着值语义需求又存在着引用语义需求。由于所有的引用语义需求都可以在值语义的基础上进行构建实现,加上引用语义在实现和应用上的限制,通常认为 C++ 标准库中只提供基于值语义的容器类就足够了——其余的在此基础上由用户去实现吧。
另外,无论是哪种容器,它代表的其实是一种数据结构和算法。每个容器都有自己的特点和缺陷,选择了其中之一也就意味着同时接受了它的缺点,所以到底哪种容器适合使用不是单一方面所能决定的,这中间还存在着一个综合平衡和使用的问题。比如你上面提到的 vector 重新分配内存和拷贝的问题,可以通过事先为
vector 分配内存、或者用 list 代替 vector 等方法避免这个问题,而有时这样的问题不可避免。通常要综合考虑多方面的情况,才能决定一种适合问题解决的、代价最小的方案。
wolf0403 回复于:2006-09-30 00:34:20
呵呵,首先为『晚生不以为然』的疏漏表示抱歉。。:)
我们回到最初的问题。楼主为了能够在删除 map 元素的同时为 map 中被映射对象(mapped_type)调用 delete 释放而派生了 std::map,您就此提出『STL 的设计中没有使用虚函数机制』以及『而如果一个类的析构函数是非虚函数的话,就决定了不适合从这个类派生其它类型』。实际上错误是从这里开始的。首先,STL 的容器设计确实没有采用虚函数,但是这并不代表我们不能从这些容器类派生。虚拟函数的作用只存在于我们需要多态行为的时候,也就是说,我们需要通过基类接口操作派生类对象的时候才会起到作用。而这里,很明显的,楼主的代码实际只是在重用 std::map 的实现代码和接口,并不希望客户程序将这个 auto_map 当作一个 std::map 使用,因此,std::map 是否使用了虚拟函数已经无关紧要。
实际上,可以翻开 STL 源码看看。包括 vector 在内的容器,在 STL 中很常见的实现都包含了类型的派生。使用一个基类负责内存的分配和管理,而最终的 vector 等容器类专注于容器语意的实现。这种纯粹的代码重用是不需要借助于虚拟函数的,也并不违反 STL 中『高效』的设计初衷。
其次,楼主的代码其实更好(或者说,更符合教科书理论)的实现方案是,将 auto_map<T> 私有派生自 std::map<T>,然后重新实现这个接口。私有继承作为 C++ 中比较独特的一种继承手段,实际是专门用于表达『实现继承』的概念的。私有继承后,可以通过 using 语法公开可以直接重用的接口,而自己实现不可直接重用的接口。这样就可以在编译器层面防止『从 std::map 引用或指针操作 auto_map』这种错误的产生了。
我的第一个回答是误解了题意,而随后关于值语意和引用语意的容器中,您使用错误的论据证明了错误的论题。如果有兴趣我们可以单独探讨。
wolf0403 回复于:2006-10-01 01:43:29
对于容器管理指针或者对象本身,我们可以做一个效率和实用性方面的分析。首先分析普通情况:当容器中保存的是指向对象的指针,而不是对象本身的时候,我们会在多少情况下造成效率的损失?
要说效率损失的程度,首先需要考察的是,效率损失的可能来源。有多少情况下我们需要操作对象本身?
[list=1]
[*]向容器中插入对象的时候
[*]通过迭代器引用对象的时候
[*]析构容器,删除容器管理的所有对象的时候
[/list]
另外还有一个特殊情况,就是
[list]
[*]vector / deque 重新分配内存时,引发的容器中元素的移动
[/list]
详细对比一下这三个情况下,保存指针的容器和保存对象容器的行为区别:
[list]
[*]向容器中插入对象的时候
[list]
[*]STL:复制一个对象,将这个对象的副本交由STL容器管理
[*]指针语意:取对象地址存入容器
[/list]
[*]通过迭代器引用对象的时候
[list]
[*]STL:根据容器的cv-qualifier,返回reference或const_reference,然后通过这个返回的引用来间接引用对象
[*]指针语意:直接返回对象地址
[/list]
[*]析构容器,删除容器管理的所有对象的时候
[list]
[*]STL:析构所有容器管理的对象
[*]指针语意:根据容器策略,减少对象引用记数,或者(当对象彻底交由容器管理时)析构元素
[/list]
[*]vector / deque 特殊情况下:
[list]
[*]STL:分配内存,为每个位于旧缓存中的元素在新地址上创建副本(通过copy-ctor),析构旧副本
[*]指针语意:直接复制指针到新的缓存,然后直接释放旧缓存,不涉及对象操作
[/list]
[/list]
对比分析一下可以发现,[list]
[*]情况1:当构造一个元素只是为了加入容器时,STL 会强迫带来一次额外的对象复制
[*]情景2:效率取决于引用和指针的底层实现方式,多数编译器不会有太大差别(据说部分编译器将引用实现为双重指针,则引用反而带来更大效率问题)
[*]情景3:指针语意容器明显可以提高基于策略的灵活性
[/list]
在特殊情况下,由于内存重分配是一个容器内部的过程,属于实现细节,本身不应该与对象有任何相关;而在 STL 实现中,不仅调用了额外的对象操作,甚至影响了对象的生命周期。这不仅带来效率的损失,甚至可能带来语意的差别,导致错误的出现(譬如,如果需要用容器管理一系列不可复制的对象)。
因此,简单的结论是,指针容器在所有情况下完胜基于值语意的 STL 容器。
引用:如果真是你说的那样的话,STL 容器类会毫不犹豫地使用(至少是部分使用)引用语义
请注意,前面我已经提到过,STL选用值语意而非引用语意,完全是为了遵循贯穿C++语言设计的值语意整体思路。C++的类型系统突出值语意,突出对象生命期,并极大程度鼓励程序员在实现类型的时候通过重载操作符,模拟原始值类型(int, double 等)的行为方式。这样在很多情况下固然能写出风格统一、漂亮的代码,但是当作为实现容器时仍坚持这种方式,带来的只能是一些不必要的损失。
为了解决不必要的对象复制问题,C++社区先后推出了多种解决方案:消除临时对象的 RVO/NRV优化,以及新的 move-constructor 提案。几乎可以认为,move-constructor 就是为了解决 vector 的性能和语意问题而专门提出的。所以,STL 的值语意设计绝不是完美的。它实际上只是一个语言政策的,或者说,对问题进行错误建模的牺牲品。
ps. 还有一种情况会引发单独元素访问:从容器中删除一个元素。除 vector / deque 之外,两种容器的行为都只涉及析构元素和释放内存;而对于 vector / deque 可能引发部分元素的移动,归入前文的特殊情况讨论。
[ 本帖最后由 wolf0403 于 2006-10-1 08:37 编辑 ]
lileding 回复于:2006-10-01 11:18:07
用shared_ptr
xiaomiao 回复于:2006-10-01 14:44:43
看了各位就此问题的深入讨论,受益非浅!我贴上的代码段来源自国内中兴通讯公司某项目的Source Code片断,本来觉得该公司C++高人很多,但看来有这么多隐含的小问题,请哪位高手写个更好的实现?用Shared_ptr要怎么做?多谢指教!
whyglinux 回复于:2006-10-02 00:54:20
To wolf0403
经过你的指正,终于认识到我以前的在非虚析构函数类的继承上的结论过于简单化了。我上面说:“千万不要把 C++ 标准库中的容器类作为基类使用”,现在收回这句话,并重新订正自己的看法:
非虚析构函数类(比如 STL 容器类)在下列前提条件或者下列情况下可以作为基类继承使用:
[list=1][*]如果能限制用户使用基类指针删除派生类对象。限制的方法或者是在文档中进行说明,或者象 wolf0403 所说的从基类进行 private(或者 protected)继承。
[*]如果派生类的析构函数是 trivial 的。所谓 trivial 析构函数是指非用户声明的析构函数、并且类的非静态数据成员和基类(如果有的话)的析构函数也是 trivial 的,其实就是没有任何实际作用的函数。既然这样,调用与不调用这个析构函数也就没有什么关系了,所以即使通过基类指针释放派生类对象中存在的问题也就不成为问题了。
[/list]
楼主程序中派生类的析构函数是 non-trivial 的,所以可按照条件 1 来做以避免可能出现的问题;或用其它方案(如上面提到的成员法、或将指针换为 shared_ptr)等。
对于你的下面的这个结论:
>> 简单的结论是,指针容器在所有情况下完胜基于值语意的 STL 容器。
我认为并不正确。还是那句话:如果你的这个结论是正确的,那么由于容器无论是值语义还是引用语义都不影响容器的使用界面和通用性,此时效率就成为了主要的决定因素,STL 容器会毫不犹豫地是你所说的指针容器(或者说是引用语义的容器),而不是(或者不仅是)现在的基于值语义的容器。得出这样片面的结论是因为你只是从单一方面、即只从拷贝对象的消耗方面来考虑问题的,没有根据要解决问题进行综合考虑。
和基于值语义的容器一样,使用引用语义的容器也是有代价的。这个代价主要表现在两方面(上面已经说过了):间接访问方式以及引用计数。这就导致了:
[list=1][*]向容器中插入对象的时候:取对象地址存入容器,[color=red]更新引用计数[/color]。
[*]通过迭代器引用对象的时候:直接返回对象地址,[color=red]然后通过地址才能访问到对象本身[/color]。注意:不论迭代器的内部实现如何,引用语义的容器要访问对象总是分为上述两步 **iterator,而值语义的对象(即元素本身)访问只要一步 *iterator。
[*]根据容器策略,减少对象引用记数,或者(当对象彻底交由容器管理时)析构元素
[/list]
其中红字部分是你遗漏的。引用计数的存在,造成了在对象构造和析构上各多了一步操作;特别是上面的第二条,造成了遍历容器的时间是基于值语义的容器的约两倍,在很多情况下(比如容器遍历是主要处理的情况)使得引用语义容器的效率会远远低于同样的值语义容器的效率。
引用语义的主要特点是不拷贝引用对象。如果不必要的拷贝对象的消耗在问题的处理中占据矛盾的主要方面,这种情况下使用引用语义才是合适的;否则,和使用值语义的容器相比,使用引用语义容器也可能没有优势、甚至还会带来效率的降低。
wolf0403 回复于:2006-10-02 02:43:37
whyglinux
1、引用记数不是必需的。STL 容器生命周期结束后,所有保存的指向容器内对象的指针、引用和指向容器的迭代器均会失效,这里可以保证同样的策略,不需要引用记数。
2、确实要多一步间接,是我忽略了。但是,由于设计的不同,这个性能损失在某些情况下是可以通过回避使用迭代器而回避的。
#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <algorithm>
template <typename T>
struct LordOfDestructor {
void operator() ( T* pobjt ) {
delete pobjt;
}
};
template <typename T>
struct ObjFactory {
static T *newInstance() {
return new T();
}
static T *clone (const T& obj) {
return new T(obj);
}
};
template <typename T,
typename Dtor = LordOfDestructor<T>,
typename Factory = ObjFactory<T> >
struct container : private std::vector< T* > {
typedef T& reference;
typedef const T& const_reference;
typedef std::vector< T* > base_t;
struct iterator {
reference operator* () {
return **iter;
}
typename base_t::iterator iter;
iterator (typename base_t::iterator i)
:iter(i)
{}
};
// simulation for STL semantics
T * add (const T& obj) {
T *rt = Factory::clone(obj);
base_t::push_back(rt);
return rt;
}
// provide more conveniency for allocating and manipulating objects
T * add (T *p) {
base_t::push_back(p);
return p;
}
void clear() {
for_each (base_t::begin(), base_t::end(), Dtor());
}
iterator begin() {
return iterator(base_t::begin());
}
~container() {
clear();
}
};
int main ()
{
container<int> c;
int *p = c.add(2); // Since we'd never reallocate objects (as they're managed by Factory and Dtor)
container<int>::iterator iter = c.begin();
printf ("%d\n", *iter);
*p = 3; // Pointer to obj returned previously would always be valid and safe to use (MT conditions excluded).
printf ("%d\n", *iter);
c.add(3);
iter = c.begin(); // iterator may be invalid
assert (*iter == 3);
assert ((void *)&*iter == (void *)p); // but original object always holds.
system("PAUSE");
}
Quite a few different characteristics between this and STL containers though..
goodboy1881 回复于:2006-10-02 11:08:11
友情顶上面的代码:D:D:D
让建立和销毁独立,给用户选择的机会,
是很不错的做法,灵活。
lileding 回复于:2006-10-03 11:53:32
LS的LS的代码有很多的问题
首先是你实际上是特化了一个接受T但使用T*的vector,所以与其使用及其耦合的继承,还不如真去特化一个vector
第二,这个代码明显缺乏保护,如果T却是个类或者原生类型,那么以上代码可以工作,但如果T也是一个指针类型的话,程序也就离死不远了
第三,STL容器类本身会使用一个allocator,其实vector是定义为template <typename T, typename A = std::allocator<T> >的,也就是说,应当使用自己的分配器,而不是继承
第四,add函数通过暴露原生指针而直接揭露容器内部数据结构,这不尽违反了STL的值语义,更破坏了封装性
第五,让用户可以肆无忌惮的操纵容器内部数据成员,是极度不理智的行为,相当于裸奔
总之,STL容器的根本思想是值语义,这就相当于一个精美的礼品盒子,如果有人能凌空取物的话,主人会抓狂的;STL本身是希望范化而非特化,重用而非重写,标准委员会给出如此的库设计是有实际意义的,他给出了完成任务的绝佳模式,只是我们还没能全部挖掘出来。
ps. 如果希望容器析构时,内部元素也能自动析构,还是应该从shard_ptr上下手,这样还会有容器无关的优势
wolf0403 回复于:2006-10-03 13:01:23
lileding:
1、我的代码只是一个 demo snippet
2、我不是在特化 vector<T> 或 vector<T *>。请注意看,我用的是私有继承,换言之,是实现继承。这里 vector 只是作为一个方便的内存管理工具。换作直接操作 T *[] 也可以。
3、自己提供 allocator 的问题更多——首先一个问题就是你无法判断容器是在移动对象还是真的复制对象。我所提出的值语意带来的性能问题根本没有解决,甚至没有回避。另外,根据 SGI STL 文档
引用:The details of the allocator interface are still subject to change, and we do not guarantee that specific member functions will remain in future versions.
你让我怎么去信任它?
4、我从开始就声明我这是一个指针语意容器,使用 T* 作为参数当然不算『暴露内部数据结构』。相反,通过允许用户指定 Factory 和 Dtor,使得容器和用户可以使用统一的内存分配机制,允许用户直接向容器『交付』对象,提供的是 STL 所完全不能达到的灵活性。
5、是不是让用户操作一个经由 new 创建对象得到的指针就算让用户『肆无忌惮的操纵 libc / libstdc++ 内部数据成员』了呢?
总之,我从一开始就说过我是排斥 STL 值语意的,而我的容器是完全基于指针/引用语意设计,根本没有考虑 STL 兼容。一个东西放在容器里,那么他就不会凭空消失或者乾坤大挪移——如果我把一个东西放在某处,过一会发现他竟然消失了,我才会抓狂。
[color=red]盲目崇拜 std:: 是缺乏 pragmatic 精神的。[/color]
ps. shared_ptr 只是把原生指针包装得到一个值语意的间接层,纯属为了适应 STL 的拙劣设计而不得已为之。而且,如 whyglinux 所说,ref count 带来的效率影响是不能忘记的。
[ 本帖最后由 wolf0403 于 2006-10-3 13:08 编辑 ]
lileding 回复于:2006-10-03 13:50:39
看来分歧所在就是我更倾向于容器的值语义,容器于我更是放东西的盒子而不是放标签的抽屉,我个人难于接受一个指针语义的容器
从值语义的角度看,如果你把一个东西用盒子给出的方法放入后,不通过盒子就释放他,这明显是个疯狂的行为。
引用:是不是让用户操作一个经由 new 创建对象得到的指针就算让用户『肆无忌惮的操纵 libc / libstdc++ 内部数据成员』了呢?
显然这不是,因为new/delete是libstdc++给出的接口;而delete container.add(new int(10))就分别使用了两个不同的接口。当然你说他是指针语义那另当别论,只是我始终认为container这类东西还是值语义的好
至于allocator,我也并不认为自己做个allocator是什么好事;但用自己的代码去完成语言已经提供的东西更不好
至于效率问题,要么充分相信程序运可以完美的处理内存,要么总得有个机制去标识内存的使用,引用计数即使是有前面所说的额外消耗,甚至还有循环引用,但似乎还没有什么更好的自动内存记录的方式
对于std:: 要知道,标准库就是C++语言的一部分,std和new一样都是内置方法。[color=red]你说我崇拜,我只能说,我在用这个语言[/color]
[ 本帖最后由 lileding 于 2006-10-3 13:56 编辑 ]
wolf0403 回复于:2006-10-03 14:02:52
呵。返回对象装入容器后的所在地址的指针,并不是说我鼓励用户去手工释放这个对象。我前面的帖子也提到,[color=red]这个对象是『交付』给容器的[/color]。换言之,加入容器之后,用户虽然可以使用这个对象,但是不应该去释放这个对象。我的 class 只是一个 snippet。完整的版本应该加入 iterator 和容器的 erase 方法,通过容器和迭代器管理对象声明。使用时可以直接操作对象,避免二次引用带来的效能损失。
wolf0403 回复于:2006-10-03 14:09:17
http://topic.csdn.net/T/20040318/16/2857938.html
这里有我一个很久之前的讨论。当时的想法很不成熟,但是最后的结论和今天仍然是一样的。……
lileding 回复于:2006-10-03 14:18:45
使用引用语义还是感觉极度诡异
此外你的两个add就是两个不同的语义
add(const T&)内部是一个深拷贝
add(T*)则是浅拷贝
考虑这样的代码container1.add(container2.add(new int(10)))
在释放的时候会怎么样呢?
wolf0403 回复于:2006-10-03 14:33:42
呵呵,我说了,两个 add 语意不同。提交指针时,指针指向的对象应该由容器的 Factory 得到;提交后,返回的是一个指向容器内对象的指针,算是一个 weak reference。提交 const ref 纯粹为了兼容 STL 语意,进行一个深复制。实际上,这个接口是我在发帖的适合临时加入的:)
container1.add(container2.add(new int(10)))
这个代码中,new int(10) 首先没有使用 container2 的 Factory 生成,此是一错
new int 得到的指针被提交给 container2,因此不会有内存泄漏
而 container2.add 返回的是一个 weak reference,你将这个 weak reference 再次『提交』给 container1,自然是会出现运行错误的。
lileding 回复于:2006-10-03 14:51:14
首先,C++没有weak reference的概念
你的add(T*)接口没有规定我是从哪分配来的内存,即便是add(new int (Factory<int>::newInstance()) (10))我仍然添加的是指针,而且这个指针指向了一块内存
第二,我所给出的代码并不会出现运行时错误,也不会内存泄漏;相反,是在释放的时候出现delete 空指针
不管你的对象是从什么地方分配来的,只要是个指针,那么被[color=red]提交[/color]给两个你的容器里之后,就必然在析构的时候delete null_pointer,其实这跟你把auto_ptr放进vector里面是一个问题
第三,防止以上问题出现只有两个方法:要么用add(const T&),但这就是一个值语义,因为你使用了深拷贝,你在内存使用上一点儿也不省;要么增加对对象指针的所有权管理,考虑到具体类的设计者很可能会用到RAII,你只能使用引用计数,也就是shared_ptr
我看了你在csdn上的那个帖子,似乎根本上来说,就是你对把auto_ptr放入vector里面念念不忘,[color=red]但这不行[/color]
wolf0403 回复于:2006-10-03 15:30:25
首先,C/C++ 没有的概念多了,包括 C 没有『字符串』,包括 C++ 没有 concept。很多东西都是一个约定而已。我这个容器约定,提交的指针应该由与容器相同的 Factory 构造;不满意,可以交给我的容器负责自己构造一个。
其次,你把一个指针提交给两个容器根本就是错误的做法。一稿多投在很多出版社都是不受欢迎的。析构时候不会出现 delete null_pointer,而是出现 double dtor / double free。这和 vecter<auto_ptr<T> > 是完全不同的。
第三,内存节省和节省对象复制方面的问题主要出在 vector / deque 缓存的重新分配方面,因为这个过程可能[color=red]重复的、大量对象的[/color]复制。提交 const ref 是出于接受常量或常引用等不可提交对象而准备的,如 container<int> c; c.add(1); 这种情况。
Rules and contracts are to be obey, not to be challenged. Or, go ahead and write strcpy("String", NULL);
[ 本帖最后由 wolf0403 于 2006-10-3 15:39 编辑 ]
lileding 回复于:2006-10-03 16:15:23
引用:我这个容器约定,提交的指针应该由与容器相同的 Factory 构造;不满意,可以交给我的容器负责自己构造一个。
我之所以用placement operator new就是告诉你不管谁构造都是一个意思
引用:你把一个指针提交给两个容器根本就是错误的做法。
不要说你的容器不能使用标准算法,我可是要std::copy的
引用:内存节省和节省对象复制方面的问题主要出在 vector / deque 缓存的重新分配方面
出现重新分配是你程序写得不好,不确定容量就得用reserve,push_front就不能用vector,这都不知道那就没办法了
你始终没能解决把你的容器作为范化容器使用的问题,如果你需要让使用你代码的人接受一千条规定的话,那就没必要讨论了
wolf0403 回复于:2006-10-03 16:30:18
1、placement new?抱歉,没注意到您在哪里谈到过。
2、是不是除了我的容器,std::copy 已经兼容天下所有容器库了?还是只有当那些容器属于对 STL 的扩充的时候,std:: 的算法才能应用呢?一个框架有一个框架的范围,我说了,我的容器不属于 STL 框架,也不兼容 STL 框架,因为从语意到设计思路都是不同的。
3、呵呵,就像您有意将一个对象分别提交给我的两个容器,我为什么不能对 vector 进行 push_front?编译器并没有阻止我,不是么?
您所谓的范化,始终仅限于 STL 框架内。我即使另写一套值语意的容器,仍然可以做到不兼容 STL。
使用 STL 容器和算法,我需要精心设计我的对象(或容器和迭代器,如果要兼容 std algorithm),通过(个人认为丑陋而多余的)操作符重载来达到 STL 容器中暗含的某些奇特要求,这可比我的容器所做出的约定数量更多多且不可理喻多了。
如果反对我的观点,请您告诉我:
[color=red]为什么 STL 不允许我对 map 和 set 执行 remove_if ?[/color]
是标准委员会的老爷们认为说从 map 或 set 中有选择删除某些对象是完全无理取闹的行为么?
lileding 回复于:2006-10-03 16:48:31
1. 麻烦您仔细看我写的代码
2. 那告诉我您的那个容器是干什么的?就为了让您后面的main能编译运行是么?
3. vector.push_front可不会去玩空指针,效率低下与dump不是一个概念
你把引用语义用于STL自然会遇到各种各样的隐晦、丑陋且多余的编译器报告,自己用多余的概念写多余的库
你永远要满足语义的需求,正像list还提供了自己的sort,这就是list和sort共同表达的意思
你维护了一个脆弱,孤独,不和任何库相同的指针集合;即把最珍贵的资源委托于他人,又让你同时有能力去疯狂的摆弄;你的指针不能delete,不能放到不同的容器中,不能应用于已有的算法
[color=red]仅仅为了减少一个4字节的引用计数而重写整套库,这到底是去反抗委员会老爷们的圣旨,还是精力过剩?[/color]
wolf0403 回复于:2006-10-03 17:08:20
1、代码太多了,您提示一下方便大家不可以么?恕我眼拙,我确实没有找到任何一个代码中出现过 placement new 。
2、第 N+1 次向您解释这个问题:我的代码只是一个片段,并没有形成一个完整的框架,所以要求实现所有的算法是不切实际的。如果真的有人有兴趣,我大可以把整个 STL 移植到我的这个框架上来。
3、再次告诉您,首先,我这里也没有空指针。其次,效率低下和错误当然不是一个概念。相比效率低下的代码,出错的代码更容易引起程序员的警觉。除非 STL 是在鼓励程序员『实现功能就行,效率是可有可无的东西』,否则只能说 STL 在这点上反而不如我的框架对程序员提供了更多的帮助。
4、关于『各种各样的隐晦、丑陋且多余的编译器报告』,能否请给出几个例子?
5、请问您说的『语义的需求』是谁的需求?是程序员、对象本身,还是 STL?[color=red]为什么 map 和 set 不能有选择删除元素?[/color]
6、指针和对象是两个概念。根据我默认提供的一组 Factory / Dtor,这个容器是通过指针管理对象;而如果你需要另外一个容器来操作指针,可以重写一组 Factory / Dtor,也可以使用 STL ——当不需要通过指针、描述符或者其它相对复杂的机制管理对象而仅仅是直接摆弄几个对象的时候,STL 的确做得很好。
7、我的方案保证了在使用我的容器管理对象的同时,可以通过最直接的方式使用这些对象。如果你认为 shared_ptr<T> 和 T 的区别只是一个 4bytes / object 的 space overhead,那么我只能认为您或者是在有意忽略一个额外间接层带来的设计复杂性、函数调用和指针转发带来的效率损失或内联、模版带来的代码膨胀;或者您确实一窍不通。
我可以接受您不理解我的设计,但是如果您只是一味地攻击却指不出更实质的设计缺陷,我只能选择沉默。
lileding 回复于:2006-10-03 17:37:40
额外的中间层解决了大量的软件问题
似乎你也没看到COM取得了多少成就以及他被取代的根本问题
你的设计问题就是你根本不应该设计这个东西,你期望能用一个管理器去管理一个给定粒度上的所有动态内存分配工作而又不付出任何代价
我从你所给出的全部代码中只能看出这根本就不是一个容器,而是让你可以把在时间上接近,用处相同的指针进行自动管理,然后随意的使用而不但心析构问题
承认吧,按照你的说法,与其是容器,不如说是个内存管理器;你一直在避讳我提出的实际代码问题,只说一句“我的代码不跟这些东西交流”,那你的代码干什么?
你说你可以实现整个你风格STL,我不是怀疑,而是根本不信,因为我从你给出的代码和描述上看不出一点儿能重用的地方
set map不能原地修改才是他不能remove_if的原因。[color=red]那不能把元素添加到两个你的容器的原因是什么?是因为你那个东西根本就不是容器,而是内存管理器,不管你怎么把Dtor/Ctor分出来[/color]
至于指针转发,内联等问题,还是先看看shared_ptr的源码和编译器的行为再说吧
ps. 好吧我承认你那是一个非常卓越的容器,那么我可不可以把所有我new出来的东西都扔里面,然后庄严的宣告,我实现了C++下的垃圾收集?
wolf0403 回复于:2006-10-03 18:12:22
前面的不讨论了,没说头,我只当你在搞笑。
如果引用:set map不能原地修改才是他不能remove_if的原因 ,为什么 map 和 set 仍然支持 erase 一个 iterator?
引用:好吧我承认你那是一个非常卓越的容器,那么我可不可以把所有我new出来的东西都扔里面,然后庄严的宣告,我实现了C++下的垃圾收集?
不妨去看看《C++沉思录》里的第 28 章……荣誉归于圣 Andrew Koenig 和圣 Barbara Moo
[ 本帖最后由 wolf0403 于 2006-10-3 18:16 编辑 ]
lileding 回复于:2006-10-03 18:23:59
好吧,你已经开始跑题了
去享受你的裸体指针吧
wolf0403 回复于:2006-10-08 00:01:36
国庆结束了,自己顶起来。希望有兴趣讨论的前辈、朋友继续发表意见。
|