1

I've got a issue with a std::vector being cleared but I can't figure out where. I have spent a lot of time debugging the issue and can't for the life of me find out where it is cleared.

Code:

    class ENGINE_API QuadTree {
    private:
        typedef std::unordered_set<QuadTreeObject*> EntityContainer;
        typedef std::vector<QuadTree> ChildContainer;

        const static int MAX_OBJECTS = 5;
        AABB bounds;

        ChildContainer children;
        EntityContainer entities;
        EntityContainer allEntities;

        // Internal methods
        void split();

        int getIndex(const AABB&)const;

        template <class T>
        void merge(T& one, const T& two) {
            one.insert(two.begin(), two.end());
        }
        // End Internal methods
    public:
        QuadTree(AABB bounds) : bounds(bounds), children(), entities(), allEntities() {
            children.reserve(4);
        }

        // Base
        void clear() {
            entities.clear();
            allEntities.clear();
            children.clear();
        }

        void insert(QuadTreeObject*);

        void remove(QuadTreeObject*, const AABB&);

        // Not the source, I've removed this and it still works fine (Nothing uses this method)
        ChildContainer& getChildren() {
            return children;
        }

        AABB& getBounds() {
            return bounds;
        }

        QuadTree getLowestRoot(const AABB& aabb)const {
            int index = getIndex(aabb);
            if (index == -1 || children.empty()) {
                return *this;
            }
            return children.at(index).getLowestRoot(aabb);
        }

        const EntityContainer& getAll()const {
            return allEntities;
        }
        // End Base
    };

void QuadTree::split() {
    children.clear(); // supposed to be there

    double width = (bounds.getWidth() / 2);
    double height = (bounds.getHeight() / 2);

    children.push_back(QuadTree(AABB(bounds.min.x + width, bounds.min.y, width, height)));
    children.push_back(QuadTree(AABB(bounds.min.x, bounds.min.y, width, height)));
    children.push_back(QuadTree(AABB(bounds.min.x, bounds.min.y + height, width, height)));
    children.push_back(QuadTree(AABB(bounds.min.x + width, bounds.min.y + height, width, height)));

    auto it = entities.begin();
    while (it != entities.end()) {
        int index = getIndex((*it)->getAABB());
        if (index != -1) {
            children.at(index).insert(*it);
            it = entities.erase(it);
        }
        else {
            it++;
        }
    }
}

int QuadTree::getIndex(const AABB& aabb)const {
    // Fits in top
    bool topQuadrant = aabb.max.y < aabb.horzMid;
    // Fits in botom
    bool bottomQuadrant = aabb.min.y > aabb.horzMid;

    // Fits in left
    if (aabb.max.x < aabb.vertMid) {
        if (topQuadrant) {
            return 1;
        }
        else if (bottomQuadrant) {
            return 2;
        }
    }
    // Fits in right
    else if (aabb.min.x > aabb.vertMid) {
        if (topQuadrant) {
            return 0;
        }
        else if (bottomQuadrant) {
            return 3;
        }
    }

    return -1;
}

void QuadTree::insert(QuadTreeObject* object) {
    AABB aabb = object->getAABB();

    allEntities.insert(object);

    if (children.empty()) {
        if (entities.size() <= MAX_OBJECTS) {
            entities.insert(object);
            return;
        }
        split();
    }

    int index = getIndex(aabb);
    if (index != -1) {
        children.at(index).insert(object);
    }
    else {
        entities.insert(object);
    }
}

void QuadTree::remove(QuadTreeObject* object, const AABB& aabb) {
    if (!allEntities.erase(object)) {
        return;
    }

    if (!entities.erase(object)) {
        if (!children.empty()) {
            int index = getIndex(aabb);

            if (index != -1) {
                children.at(index).remove(object, aabb);
            }
            else {
                throw std::logic_error("Can't be in allEntities but not entities!");
            }
        }
        else {
            throw std::logic_error("Can't be in allEntities but not entities!");
        }
    }
}

When the split method is called children.size() returns 4, but by the time it gets to insert it is 0 again. How is this possible?

Tips48
  • 745
  • 2
  • 11
  • 26
  • Can you post a main() program showing how you use these classes? It is hard just to look at so much code and run the computer in our heads. But one thing I could suggest is that you should make proper usage of algorithms that remove, partition, and erase, rather than writing loops to do the erasure "by hand". – PaulMcKenzie Mar 26 '14 at 23:20
  • @PaulMcKenzie you're suggestion I split up the QuadTree::split method more? Or are there other cases – Tips48 Mar 26 '14 at 23:28
  • It looks like the real data structure to store within a container (or two) is the `AABB` data. Is `AABB` a typedef from a struct? Reviewing a sample from SO: [R-Trees : should I reinvent the wheel?](http://stackoverflow.com/questions/14195111/r-trees-should-i-reinvent-the-wheel), indicates AABB can be a typedef based on `boost::geometry::model::box` and `boost::geometry::model::point`. Is that how `AABB` is defined here? – CPlusPlus OOA and D Mar 27 '14 at 00:20
  • `AABB` is its own class consisting of two `Vec2D`'s (`min` and `max`) and some helper methods. – Tips48 Mar 27 '14 at 00:23
  • How is Vec2D defined? Is it from a vendor supplied header and cpp? – CPlusPlus OOA and D Mar 27 '14 at 00:49
  • Please, please, *never use pointers as keys into a set or map*. `typedef std::unordered_set EntityContainer;` You are practically begging for weird things to happen if that pointer is invalidated, gets reassigned to another item, etc. Use objects or simple integral types, not pointers. I can't tell you how many bugs appear because of definitions like this... – PaulMcKenzie Mar 27 '14 at 00:53
  • As pointed out by others already, there are basic implementation choices which will have to be refactored. The answer on Vec2D will help understand what is intended versus how it is currently. – CPlusPlus OOA and D Mar 27 '14 at 00:56
  • Vec2D is my own implementation, but basically it's just a struct with a public `x` and `y` and some helper methods (Normalize, length, etc) – Tips48 Mar 27 '14 at 22:26

1 Answers1

2

This is a problem:

class ENGINE_API QuadTree {
    typedef std::vector<QuadTree> ChildContainer;

    ChildContainer children;

QuadTree is an incomplete type up until the class definition is finished. If you use std::vector with an incomplete type, the behaviour is undefined.

In other words, a class can't contain a vector of itself. You will have to redesign your class a bit, e.g.:

  • switch to a vector of smart pointers,
  • implement the vector in a pImpl idiom
  • use a different container that is designed to work with incomplete types
M.M
  • 138,810
  • 21
  • 208
  • 365