0

This follows on from my previous question, where I was slow to supply all the information so I am creating a new question in the hope of more input. Given:

    struct otherClass {
        ImportantObj    *ptrToImpObj;
        otherClass() {ptrToImpObj = NULL;}
    };
    struct anEntry {
        Thing *thing;
        std::vector<otherClass> *iDM2P2H;
        anEntry(Thing *tg, std::vector<sqdDMPair> *dM2Pair = NULL)
            : thing(tg), iDM2P2H(dM2Pair) {}
        ~anEntry() {delete iDM2P2H;}
    };
    std::vector<anEntry *> aQueue;
    std::vector<anEntry> bQueue;
    void aMethod () {
        Thing thingy = &(masterArrayOfThings[42]);
        aQueue.push_back(new anEntry(thingy, iDM2P2H));
    }
    void bMethod () {
        Thing thingy = &(masterArrayOfThings[42]);
        bQueue.push_back(anEntry(thingy, iDM2P2H));
    }

The second method will invoke the dtor on the memory shared between the two objects involved with the copy constructor.

In this case I feel I should be using pointers inside my vector and aQueue is preferable to bQueue.

Thanks.

_EDIT_

Let's say I'll have 20 aQueue's and they will be cleared and iDM2P2H replaced hundreds (thousands?) of times a second as the AI route evaluation sees fit.

John
  • 5,995
  • 6
  • 42
  • 75
  • 1
    1 thing at a time. Your anEntry class is horrible. Fix that first. – Benjamin Lindley Aug 27 '11 at 23:32
  • I mean make it handle it's resources properly. [Read this](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) for starters. – Benjamin Lindley Aug 27 '11 at 23:35
  • Rather than following the "rule of 3" and deep copying to satisfy my dtor, it would be more maintainable to use pointers in the stead of objects. – John Aug 27 '11 at 23:42
  • You need reference-counting then. – Jim Buck Aug 27 '11 at 23:46
  • 1
    @John, you should follow the "rule of 3" OR forbid access to the copy constructor/operator=. Failing to implement all three but still leaving the default implementations in place is a bug waiting to happen. – bdonlan Aug 27 '11 at 23:46
  • I don't think you understand how simple this would be with pointers, the 'classes' or 'structs' are merely groupings of primitives and pointers to save having parallel arrays. – John Aug 27 '11 at 23:47
  • @John: That's the most absolutely ridiculous thing I've heard. But I won't argue with you about it. – Benjamin Lindley Aug 27 '11 at 23:48
  • @bdonlan, ok, good call, I'll ditch the copy ctor/op= – John Aug 27 '11 at 23:49
  • @John - you need to follow the "rule of 3" or your class will be far too fragile. One way to deal with the "rule of 3" without having to perform deep copying is to make the class non-copyable: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin – Michael Burr Aug 27 '11 at 23:50
  • If your class is meant to share the vectors, then use shared_ptr. Then you don't have to worry about copy constructors *or* destructors. – Benjamin Lindley Aug 27 '11 at 23:51
  • It's not meant to share, but as you see by using objects in vectors I need to assign. You are all being very pro-structured object orientation. I must get back to some lead fumes. – John Aug 27 '11 at 23:56

4 Answers4

2

About deletion of iDM2P2H, either now or ever in the future, your program is going to cause that error. If you set the same pointer in two objects, sooner or later they will both die and their destructors will try to delete the same memory. If you use pointers and new the objects, the problem persists when you delete the anEntry objects.

The solution is simply to avoid deleting iDM2P2H in the anEntry destructor, and delete it in the same context of whoever created it. That is for example if it was created at program startup, you could delete it when you have finished your need for it, in the main execution path of the program.

Shahbaz
  • 42,684
  • 17
  • 103
  • 166
1

Your problem here is your anEntry copy constructor is broken. The default copy constructor (anEntry (const anEntry &)) simply copies all members; with your class's explicit destructor, this results in double freeing. The same applies for the default operator=. Per the Rule of Three, if you define any one of a destructor, copy constructor, and operator=, you should generally implement the other two (or forbid them by making them private and unimplemented); otherwise there's a good chance the default implementation of one of them will cause problems like this.

Now, the vector class requires a working copy constructor. This is part of the vector contract. If your class has a missing (ie, forbidden by making it private) copy constructor, the compiler will error out, preventing these "serious side effects". If the class has a broken copy constructor, well, that's not vector's fault.

Note that you may want to consider using RAII-style allocation in your anEntry class. For example, make iDM2P2H a std::vector<otherClass> instead of std::vector<otherClass> *. By doing so, you won't need a destructor at all, and if the default copy semantics are acceptable to you, you might be able to do with the default copy constructor in this case.

That said, vector's copying may indeed entail significant overhead at times. There are a number of things you can do to work around this; however I would recommend against a raw std::vector<anEntry *> - the reason being that this won't clean up the pointed-to elements automatically.

Instead, use a std::vector<std::unique_ptr<anEntry>> (if you have a C++0x compiler) or boost::ptr_vector<anEntry>. This will give you the automatic destruction benefits of vector but will not copy any elements (as the vector is a vector of pointers to objects). Note that in the unique_ptr case you will need to use std::move to add elements to the vector.

Alternately, if your compiler supports C++0x move-aware containers, you could write a move constructor:

struct anEntry {
    Thing *thing;
    std::vector<sqdDMPair> iDM2P2H;

    anEntry(Thing *thing_, std::vector<sqdDMPair> *vec = NULL)
      : thing(thing_), iDM2P2H(vec ? *vec : std::vector<sqdDMPair>())
    { }

    // Default copy constructor and destructor OK

    // Move constructor
    anEntry(anEntry &&src)
      : thing(src.thing), iDM2P2H(std::move(src.iDM2P2H)) { }

    anEntry &operator=(anEntry &&other) {
      if (this != &other) {
        thing = other.thing;
        iDM2P2H = std::move(other.iDM2P2H);
      }
    }
};

Using std::move allows the contents of iDM2P2H to be moved to the new position in the outer vector without copying recursively. However, C++0x support is still in its infancy, and your compiler and STL may or may not support it yet (if std::vector<std::unique_ptr<int>> compiles, you're probably ok).

Community
  • 1
  • 1
bdonlan
  • 205,037
  • 27
  • 244
  • 316
  • 1
    Or `std::tr1::shared_ptr`. Less good than C++0x, but better than Boost. – Nemo Aug 27 '11 at 23:49
  • Sorry, I hadn't written the copy ctor as it would be easier not to have use of it. – John Aug 27 '11 at 23:50
  • @John, if you don't want the copy ctor, fine, _delete it_. Boost has a nice `boost::noncopyable` class for this. But don't leave a copy constructor around that will silently cause heap corruption if you accidentally use it :) – bdonlan Aug 27 '11 at 23:55
0

It depends on how expensive and big your objects are. Having a vector of objects is ok if they're small, but if they're big or expensive1, all the copying/constructing/destructing can add up to a large performance hit, in which case you should use pointers (and then you have to manage the memory for all the objects, etc).

1 A class can be small and expensive if it doesn't have many data members (so it's not big in size) but manages expensive resources that are changed when it is copied, destroyed, or created.

Seth Carnegie
  • 70,115
  • 19
  • 169
  • 239
0

If you really have objects that contain pointers to shared data/resources, maybe you could consider using std::shared_ptr<my_shared_type> rather than just having delete called in the dtor.

Using std::vector<> of pointers might make the issues go away for awhile, but it's potentially just masking a more fundamental issue of shared resource management.

Hope this helps.

Darren Engwirda
  • 6,660
  • 3
  • 19
  • 41