修改集合内对象的可变成员是安全的吗?

6

我很好奇以下情况是否安全。

我有以下类定义:

class ActiveStatusEffect
{
public:
    StatusEffect* effect;
    mutable int ReminaingTurns;
    ActiveStatusEffect() : ReminaingTurns(0)
    {
    }
    //Other unimportant stuff down here
}

然后我将一组这些对象存储在std::set中,如下所示:

struct ASECmp
{
    bool operator ()(const StatusEffects::ActiveStatusEffect &eff1, const StatusEffects::ActiveStatusEffect &eff2)
    {
        return eff1.effect->GetPriority() < eff2.effect->GetPriority();
    }
};
std::set<StatusEffects::ActiveStatusEffect, ASECmp> ActiveStatusEffects;

我将RemainingTurns标记为可变的,因为我希望能够在不必不断删除/插入集合的情况下更改它。即

void BaseCharacter::Tick(Battles::BattleField &field, int ticks)
{
    for (auto effect = ActiveStatusEffects.begin(); effect != ActiveStatusEffects.end();)// ++index)
    {
           auto next = effect;
            ++next;
        if (effect->effect->HasFlag(StatusEffects::STATUS_FLAGS::TickEffect) && effect->ReminaingTurns > 0)
        {                       
            effect->effect->TickCharacter(*this, field, ticks);
            --effect->ReminaingTurns;

        }
        if (effect->ReminaingTurns == 0)
        {
            ActiveStatusEffects.erase(effect);
        }
        effect = next;
    }
}

我有所顾虑,因为这可能会破坏集合内的排序,这意味着我无法保证集合始终按effect->GetPrority()排序。如果是这样,是否有一种安全的方法(例如不将RemainingTurns作为键的一部分)可以做到这一点,而不是复制、修改、删除然后插入需要更改的内容?
编辑:
@ildjarn - 对不起,我认为那并不重要。它只返回存储在StatusEffect中的int。该int保证在程序运行时不会改变。
int StatusEffect::GetPriority() const
{
    return StatusPriority;
}

听起来更适合使用 std::priority_queue<> 容器;对于更复杂的情况,请参考 Boost MultiIndex - sehe
@sehe:优先级队列能够处理更改键值而无需重新插入吗? - Björn Pollex
2
我们怎么可能知道RemainingTurns的可变性是否重要,当我们看不到StatusEffect :: GetPriority()的实现时? - ildjarn
1
@Space_C0wb0y:std::priority_queue无法处理更改键而不重新插入的情况,也无法处理重新插入,因为它根本不允许您访问非顶部元素。我曾经遇到过这种情况,不得不重新实现它(实现了Dijkstra算法,需要一个)。Boost.Graph有一个可以处理更新的优先级队列,但是它深深地挖掘了“细节”,并且要求每个对象都具有适合作为数组索引的ID,这在任何情况下都是阻碍。 - Jan Hudec
2
如果您有一个关键部分和一个数据部分,为什么不使用映射? - Bo Persson
相关:https://dev59.com/UHNA5IYBdhLWcg3wku3O - Ciro Santilli OurBigBook.com
3个回答

7
更改影响对象排序的数据确实会破坏关联容器的不变性,但由于ActiveStatusEffect :: ReminaingTurnsActiveStatusEffect 对象的排序中没有任何作用,因此保持其mutable并修改其值是完全无害的。
“我担心这可能会搞乱集合中的排序,这意味着我不能保证集合总是按effect->GetPrority()排序。”
这是一个std :: set ; 它如何按除ASECmp定义的标准以外的任何标准进行排序?

我担心它会损坏键,并且一旦发生这种情况,未来的插入将无法正确插入。 - Megatron
@user127817:但是就标准有序关联容器而言,ActiveStatusEffect::ReminaingTurns并不是键的身份的一部分,因为它在ActiveStatusEffect的排序中没有被考虑进去。也就是说,你未来唯一可能破坏的风险就是改变ActiveStatusEffect的排序逻辑以某种方式使用ReminaingTurns - ildjarn

2
如果你在std :: set中更改某个内容的键,你就会进入未定义行为领域 - 就这么简单。它不仅会“搞乱排序”,而且set可能会完全停止正常工作。

0
如果键与实际对象无关,或者只是其中的一部分,那么你应该考虑使用映射而不是集合:
std::map< int, ActiveStatusEffect > m;
ActiveStatusEffect x = create();
m[ x.effect->GetPriority ] = x;      // !!!

你的代码还存在一些问题,应该使用一些封装技术(用户代码不应该访问类的内部(即成员变量不应该是公共的))。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接