2

我之前搜索过,但找不到任何答案。我对 c++ 有点陌生,所以希望这个问题不会太愚蠢。

我正在尝试在向量中添加和删除元素,在我的情况下,在所有粒子的大更新或绘制循环期间填充了粒子。例如删除一些粒子,因为它们已经死亡,但也添加一些其他粒子,因为一个粒子与一个物体碰撞,我想在碰撞点显示一个小粒子爆发。我在演示文件中制作了这个简单的测试代码,以深入了解问题的根源。

我认为问题是因为我删除并添加了迭代器指针变得无效的粒子。删除有效,但是当我添加一些随机的时,我得到一个空指针。下面的代码有点冗长,我知道我应该使用带有 begin() 和 end() 的迭代器,但我遇到了同样的问题,并且稍微使用了代码,尝试更多的 javascript 数组样式循环,因为我更熟悉那。

void testApp::drawParticles()
{

    int i=0;
    int max = particles.size();
    vector<Particle*>::iterator it = particles.begin();

    while ( i < max ) {

        Particle * p = particles[i];

        if ( ofRandom(1) > .9 ) {
            it = particles.erase(it);
            max = particles.size();
        } else {
            ofSetColor(255, 255, 0);
            ofCircle( p->x, p->y, p->z, 10);

            if ( ofRandom(1) < .1 ) addSomeNewOnes();
            i++;
            it++;
        }
    }


}

void testApp::addSomeNewOnes()
{
    int max = ofRandom(4);

    for ( int i=0; i<max; i++ ) {
        Particle * p = new Particle();
        p->x = ofRandom( -ofGetWidth()/2, ofGetWidth()/2 );
        p->y = ofRandom( -ofGetHeight()/2, ofGetHeight()/2 );
        p->z = ofRandom( -ofGetHeight()/2, ofGetHeight()/2 );
        particles.push_back( p );
    }
}
4

7 回答 7

6

每次插入 avector时,它的迭代器都可能失效。你不可以做这个:

if ( ofRandom(1) < .1 ) addSomeNewOnes();
it++

因为调用addSomeNewOnes(),之后it是无效的。

您可以使用调用返回的迭代器vector::insert,但在您的情况下,这意味着重新设计您的代码。

无论如何,这是您可能想要做的事情,因为您的代码有点笨拙。

于 2012-06-15T16:19:16.497 回答
1

您可以从最后循环它,这应该允许您删除当前的(因为您只是从末尾删除)并添加新的添加到末尾:

Vector<Particle*>::iterator it = particles.end();

while (iter != particles.begin()) {

    Particle * p = *iter;

    if ( ofRandom(1) > .9 ) {
        particles.erase(iter);
    } else {
        ofSetColor(255, 255, 0);
        ofCircle( p->x, p->y, p->z, 10);

        if ( ofRandom(1) < .1 ) addSomeNewOnes();
    }
    iter--;
}

如果您不添加,根据此处的信息,STL 中的迭代器是稳定的,因此您应该能够向前迭代并且仍然能够获得相同的结果。

于 2012-06-15T16:22:21.943 回答
0

当底层数据发生变化时,迭代器在某些情况下会失效。

您必须跳出循环并重新开始。

您应该能够通过将整个 drawParticles 函数包装在一个while(notDone)循环中来做到这一点,并在完成矢量修改后将 notDone 设置为 true。

这是另一个关于规则的 SO 问题:Iterator invalidation rules

于 2012-06-15T16:17:25.843 回答
0
it = particles.erase(it);

将返回一个迭代器,指向删除后元素的新位置。如果擦除的那个恰好是最后一个,它将指向particles.end()it++“结束”是一个错误。

此外,如果:

if ( ofRandom(1) < .1 ) addSomeNewOnes();

评估为 true 并被addSomeNewOnes()调用,正如其他人所说,这也会使迭代器无效。

于 2012-06-15T16:21:54.360 回答
0

你是在迭代器的位置插入和删除吗?如果是这样,插入和擦除返回的迭代器将是有效的,你可以使用它们。就像是:

std::vector<T>::iterator i = v.begin();
while ( i != v.end() ) {
    if ( someCondition ) {
        i = v.erase( i );
    } else {
        ++ i;
    }
}

是一个或多或少的标准习语。

对于随机插入和删除,您必须使用索引,根据插入或删除的元素数量以及插入或删除是在当前位置之前还是之后进行更新。

于 2012-06-15T16:26:06.997 回答
0

有一种简单的方法可以使这项工作正常工作,而无需担心失效规则:只需为下一次迭代构建一个向量。

typedef vector<Particle*> ParticleVector;

// modifies the particle vector passed in 
void testApp::drawParticles(ParticleVector &particles)
{
    ParticleVector next;
    next.reserve(particles.size()); // seems like a reasonable guess

    for (auto it = particles.begin(); it != particles.end(); ++it)
    {
        Particle *p = *it;

        if (ofRandom(1) > .9) {
            // don't copy to the next cycle
            delete p;
        } else {
            // copy to the next cycle
            next.push_back(p);

            ofSetColor(255, 255, 0);
            ofCircle(p->x, p->y, p->z, 10);

            if (ofRandom(1) < .1)
                addSomeNewOnesTo(next);
        }
    }
    particles.swap(next);
}

请注意,当您不使用全局变量时,像这样重构是多么容易,顺便说一句。

void testApp::addSomeNewOnesTo(ParticleVector &particles)
{
    int max = ofRandom(4);

    for ( int i=0; i<max; i++ ) {
        Particle * p = new Particle();
        p->x = ofRandom( -ofGetWidth()/2, ofGetWidth()/2 );
        p->y = ofRandom( -ofGetHeight()/2, ofGetHeight()/2 );
        p->z = ofRandom( -ofGetHeight()/2, ofGetHeight()/2 );
        particles.push_back( p );
    }
}
于 2012-06-15T16:35:39.807 回答
0

另一方面:您的实现中没有内存泄漏吗?您正在使用

vector<Particle*>  particles

并且还使用particles.erase(). 那不会只是删除指向粒子的指针,而不是创建对象吗?

于 2012-06-15T16:40:26.660 回答