C++ 建筑C++/关于外部系统删除O(1)列表的迭代器用法的STL问题

C++ 建筑C++/关于外部系统删除O(1)列表的迭代器用法的STL问题,c++,architecture,stl,containers,iterator,C++,Architecture,Stl,Containers,Iterator,这是一个相当直截了当的架构问题,但它已经困扰了我很多年了 无论如何,对我来说,使用列表的全部意义在于它是O(1)插入/删除。 删除O(1)的唯一方法是为erase()使用迭代器。 获取迭代器的唯一方法是从初始insert()开始保持它,或者通过迭代找到它 那么,传递什么;迭代器还是指针 看起来,如果快速删除很重要,比如某种大的列表变化非常频繁,那么应该传递一个迭代器,如果不担心在列表中查找项目的时间,那么传递指针 以下是一个典型的简化示例: 在这个例子中,我们有一个叫做Foo的类型。Foo可能是

这是一个相当直截了当的架构问题,但它已经困扰了我很多年了

无论如何,对我来说,使用列表的全部意义在于它是O(1)插入/删除。 删除O(1)的唯一方法是为erase()使用迭代器。 获取迭代器的唯一方法是从初始insert()开始保持它,或者通过迭代找到它

那么,传递什么;迭代器还是指针

看起来,如果快速删除很重要,比如某种大的列表变化非常频繁,那么应该传递一个迭代器,如果不担心在列表中查找项目的时间,那么传递指针

以下是一个典型的简化示例:

在这个例子中,我们有一个叫做Foo的类型。Foo可能是一个基类指针,但这里并不是为了简单

然后我们有FooManger,它包含一个共享的\u ptr列表,FooPtr。一旦对象被传递给它,管理器就负责它的生命周期

现在,从addFoo()返回什么? 如果我返回一个FooPtr,那么我永远无法从O(1)中的列表中删除它,因为我必须在列表中找到它。 如果我返回一个std::list::迭代器,FooPtrListIterator,那么只要取消引用迭代器,我就可以在需要删除FooPtr的任何地方删除它

在这个例子中,我有一个虚构的例子,一个在某些情况下可以自杀的Foo,Foo::killWhenConditionMet()

想象一下,某个Foo有一个计时器正在滴答滴答地指向0,此时它需要请求管理器删除自己。问题是“this”是一个裸Foo*,所以删除它自己的唯一方法是使用原始指针调用FooManager::eraseFoo()。现在,管理器必须搜索对象指针以获得迭代器,以便从列表中删除并销毁它

唯一的解决方法是将迭代器存储在对象中。i、 e Foo有一个FooPtrListIterator作为成员变量

struct Foo;

typedef boost::shared_ptr<Foo> FooPtr;
typedef std::list<FooPtr> FooPtrList;
typedef FooPtrList::iterator FooPtrListIterator;

struct FooManager
{
    FooPtrList l;

    FooPtrListIterator addFoo(Foo *foo) {
        return l.insert(l.begin(), FooPtr(foo));
    }
    void eraseFoo(FooPtrListIterator foo) {
        l.erase(foo);
    }
    void eraseFoo(Foo *foo) {
        for (FooPtrListIterator it=l.begin(), ite=l.end(); it!=ite; ++it) {
            if ((*it).get()==foo){
                eraseFoo(it);
                return;
            }
        }
        assert("foo not found!");
    }
};

FooManager g_fm;

struct Foo
{
    int _v;

    Foo(int v):_v(v) {
    }
    ~Foo() {
        printf("~Foo %d\n", _v);
    }
    void print() {
        printf("%d\n", _v);
    }
    void killWhenConditionMet() {
        // Do something that will eventually kill this object, like a timer
        g_fm.eraseFoo(this);
    }
};

void printList(FooPtrList &l)
{
    printf("-\n");
    for (FooPtrListIterator it=l.begin(), ite=l.end(); it!=ite; ++it) {
        (*it)->print();
    }
}   

void test2()
{
    FooPtrListIterator it1=g_fm.addFoo(new Foo(1));
    printList(g_fm.l);
    FooPtrListIterator it2=g_fm.addFoo(new Foo(2));
    printList(g_fm.l);
    FooPtrListIterator it3=g_fm.addFoo(new Foo(3));
    printList(g_fm.l);

    (*it2)->killWhenConditionMet();

    printList(g_fm.l);
}
structfoo;
typedef boost::shared_ptr FooPtr;
typedef std::list FooPtrList;
typedef FooPtrList::迭代器fooptrlister;
结构管理器
{
食物列表l;
FooPtrListIterator addFoo(Foo*Foo){
返回l.insert(l.begin(),FooPtr(foo));
}
无效擦除foo(FooPtrListIterator foo){
l、 擦除(foo);
}
无效擦除Foo(Foo*Foo){
for(FooPtrListIterator it=l.begin(),ite=l.end();it!=ite;+it){
if((*it.get()==foo){
擦除(it);
返回;
}
}
断言(“未找到foo!”);
}
};
食品经理g_fm;
结构Foo
{
int_v;
Foo(int v):\u v(v){
}
~Foo(){
printf(“~Foo%d\n”,\u v);
}
作废打印(){
printf(“%d\n”,_v);
}
void killwhen条件满足(){
//做一些最终会杀死这个物体的事情,比如定时器
g_fm.eraseFoo(本);
}
};
作废打印列表(FooPtrList&l)
{
printf(“-\n”);
for(FooPtrListIterator it=l.begin(),ite=l.end();it!=ite;+it){
(*it)->print();
}
}   
void test2()
{
FooPtrListIterator it1=g_fm.addFoo(新Foo(1));
打印列表(g_fm.l);
FooPtrListIterator it2=g_fm.addFoo(新Foo(2));
打印列表(g_fm.l);
FooPtrListIterator it3=g_fm.addFoo(新Foo(3));
打印列表(g_fm.l);
(*it2)->killWhenConditionMet();
打印列表(g_fm.l);
}
因此,我的问题是: 1.如果一个对象需要删除它自己,或者让其他系统删除它,在O(1)中,我是否必须在对象内部存储一个对象的迭代器?如果是这样,是否存在迭代器由于其他容器迭代而变得无效的问题

  • 有没有其他方法可以做到这一点

  • 作为一个附带问题,有人知道为什么和的“push*”stl容器操作不返回结果迭代器,这意味着必须使用“insert*”


  • 请不要回答“不要预先优化”,这会让我发疯这是一个体系结构问题。

    C++标准在其[list.modifiers]部分中指出,任何列表插入操作“不会影响迭代器和引用的有效性”,任何删除操作“仅使迭代器和对已删除元素的引用无效”。所以保持迭代器是安全的

    将迭代器放在对象中似乎也是明智的。特别是如果您不将它们称为迭代器,而是将它们命名为
    FooManagerHandlers
    ,它们由删除函数以不透明的方式处理。实际上,您并没有存储“迭代器”,而是在一个有组织的结构中存储对象的“代表”。这些表示用于定义对象在该结构中的位置。这是一个独立的、相当高层次的概念,在实现它时没有任何不合逻辑的地方


    然而,使用列表的意义不仅仅是O(1)插入/删除,还在于保持元素的顺序。如果您不需要任何顺序,那么您可能会发现哈希表更有用。

    我看到在对象中存储迭代器的一个问题是,您必须小心从其他迭代器中删除对象,因为您的对象析构函数不知道它从何处被销毁,因此,在析构函数中可能会出现无效的迭代器


    push*不返回迭代器的原因是它与pop*相反,允许您将容器视为堆栈、队列或数据块。

    例如“当计时器滴答声降至0”条件,作为一个具体情况,有一个优雅的解决方案:按时间对列表进行排序,直到0为止。这样,您将始终删除第一个/最后一个元素。@Dark,然后priority queue将是一个更好的容器。@Pavel Shved,毫无疑问会这样做。计时器只是一个深奥的示例,这就是我调用函数Foo::killWhenConditionMet()的原因。它可以是任何东西,也可以是任何时间。当然,关于迭代器,它