【问题标题】:C++, A way to update Pointers after vector resize, and erase vector objects without copying?C ++,一种在矢量调整大小后更新指针并在不复制的情况下擦除矢量对象的方法?
【发布时间】:2020-06-21 22:25:22
【问题描述】:

我相信这将是我对该网站的第一个问题,因此对于本文中的任何错误或错误,我深表歉意。我也是一个初学者 C++ 程序员,如果我的问题被认为是“noobish”,请原谅我。

背景:父实体对象的集合在启动时创建(目前在运行时未删除或添加),然后链接到一系列激活器实体对象(在开始时和运行时期间)通过子实体对象。建立链接时,Parent 生成一个 Child(存储在本地向量中),并返回指向 Child 的指针供 Activator 存储。

激活器将“激活”与它们链接的子级,然后它们将根据内部和父级设置执行工作。在被激活后,它们也会由 Parent 定期更新,一直持续到最终停用。

下面是现有类的简化示例。

class ParentEntity {
    std::vector<ChildEntity> m_Children;
    std::vector<ChildEntity*> m_ActiveChildren;
public:
    //Funcs
    ParentEntity(unsigned expectedChildren) { m_Children.reserve(expectedChildren); }
    ChildEntity* AddChild(){
        m_Children.push_back(ChildEntity(*this));
        return &(m_Children.back());
    }
    void RemoveChild(unsigned iterator) {
        //Can't figure a way to remove from the m_Children list without disrupting all pointers. 
        //m_Children.erase(m_Children.begin() + iterator); Uses Copy operators, which wont work as Const values will be present in Child
    }
    void AddActiveChild(ChildEntity* activeChild) {
        m_ActiveChildren.push_back(activeChild);
    }
    bool Update(){  //Checks if Children are active, 
        if (!m_ActiveChildren.empty()) {
            std::vector<ChildEntity*> TempActive;
            TempActive.reserve(m_ActiveChildren.size());
            for (unsigned i = 0; i < m_ActiveChildren.size(); i++) {
                if (m_ActiveChildren[i]->Update()) {
                    TempActive.push_back(m_ActiveChildren[i]);
                }
            }
            if (!TempActive.empty()) {
                m_ActiveChildren = TempActive;
                return true;
            }
            else {
                m_ActiveChildren.clear();
                return false;
            }
        }
        else {
            return false;
        }
    }
};

class ChildEntity {
public:
    ChildEntity(ParentEntity& Origin) //Not const because it will call Origin functions that alter the parent
        :
        m_Origin(Origin)
    {}
    void SetActive() {
        m_ChildActive = true;
        m_Origin.AddActiveChild(this);
    }
    bool Update() { //Psuedo job which causes state switch
        srand(unsigned(time(NULL)));
        if ((rand() % 10 + 1) > 5) {
            m_ChildActive = false;
        }
        return m_ChildActive;
    }
private:
    ParentEntity& m_Origin;
    bool m_ChildActive = false;
};


class ActivatorEntity {
    std::vector<ChildEntity*> ActivationTargets;
public:
    ActivatorEntity(unsigned expectedTargets) { ActivationTargets.reserve(expectedTargets); }
    void AddTarget(ParentEntity& Target) {
        ActivationTargets.push_back(Target.AddChild());
    }
    void RemoveTarget(unsigned iterator) {
        ActivationTargets.erase(ActivationTargets.begin() + iterator);
    }
    void Activate(){
        for (unsigned i = 0; i < ActivationTargets.size(); i++) {
            ActivationTargets[i]->SetActive();
        }
    }
};

综上所述,我的三个问题是:

  1. 有没有办法在向量调整大小时更新指针?

添加 Child 时,如果超出预期容量,则向量会创建一个新数组并将原始对象移动到新位置。这会破坏所有 Activator 指针和任何 m_ActiveChild 指针,因为它们指向旧位置。

  1. 有没有办法从 m_Children 向量中删除子对象?

由于 ChildEntity 对象将在其中托管 const 项,因此复制分配操作将无法顺利进行,并且 Vector 的擦除功能将无法正常工作。 m_Children 向量可以通过临时向量和复制构造函数在没有不需要的对象的情况下重建,但这会导致所有指针再次出错。

  1. 如果有任何其他建议的优化或更正,请告诉我!

感谢大家的帮助!

【问题讨论】:

  • 没有回答您的具体问题,但您是否考虑过使用不会出现插入、调整大小和删除问题的存储(例如 std:list)?
  • std::vector 替换为std::list。您将失去缓存友好性、随机访问,并会增加内存占用,但您将消除地址失效的问题。
  • 开枪,我从来没想过名单。如果我理解正确,随机访问不是问题,因为指针访问对象,并且当前更大的内存占用是可以的。我担心缓存友好性。并非所有的孩子都在给定的时间被召唤,但大多数可能是。据我了解,Vector 在访问部分时将整个数组加载到缓存中,如果访问多个对象,这在性能方面会更快,对吧....还是错了?不管怎样,谢谢两位!我将开始测试,因为在缓存之外,这感觉是前两个问题的完美解决方案!
  • 你的问题和你的代码太长了!你应该用最少的代码问一个问题来重现问题。话虽如此,阅读文档以通过了解失效规则和性能特征来选择最佳容器是一个好的开始。 vectorunique_ptrshared_ptr 在您的情况下可能是更好的选择。如果可能,您应该尽量减少使用的容器数量,以尽量减少数据不一致的风险(例如在某些情况下忘记更新辅助数据)。如果您的数据有可用的密钥,std::map 也可能有意义。
  • 您可能会研究 std:pmr,(多态内存分配器),它可以提供竞技场分配,以使您的列表分配相对本地化以提高缓存性能。可以加快速度。

标签: c++ pointers vector stdvector


【解决方案1】:

抽象地看,您的问题是,一方面您有要迭代的对象集合,保存在容器中;另一方面,这些对象是相互关联的。重新排序容器会破坏链接。

任何问题都可以通过额外的间接方式解决:在容器中放置对象而不是对象句柄 可以在不影响交叉引用的情况下进行重新排序。最简单的情况是简单地使用指针;现代 C++ 会使用智能指针。

这里的缺点是您将转向动态分配,这通常会立即破坏局部性(尽管如果大多数分配发生在初始化期间,则可能不会)并带有通常的运行时开销。后者对于简单的、短暂的对象来说可能是令人望而却步的。

优点是处理指针使您能够使对象多态,这对于“激活器”和执行“更新”的“子”集合是一件好事:您在这里拥有的是对通常实现的接口的描述由各种具体类。将 objects 放在容器中而不是 pointers 会阻止这样的设计,因为容器中的所有对象都必须具有相同的具体类型。

如果您需要存储更多信息,您可以编写自己的句柄类来封装智能指针;或许从一开始这就是一个好主意,因为它很容易扩展,而不会影响所有客户端代码,而且开销适中(在开发和运行时)。

【讨论】:

  • 感谢您的建议!我一直在按照其他人的建议测试列表实现,但是由于大多数子项是最初创建的,并且仅在特定的修剪或链接事件期间被删除/添加到,因此对象句柄/动态内存路由可能是要走的路。我会调查的!谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-06-19
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-14
相关资源
最近更新 更多