2

I have an Entity class with a variety of subclasses for particular types of entities. The objects of the subclasses have various relationships with each other, so I was using Entity pointers and static_casts to describe these relationships and to allow entities to access information about the other entities they have relationships with.

However, I found that using raw pointers was a source of a lot of difficulty & hard to debug errors. For instance, I wasted several hours before realizing I was making pointers to an object before copying it into a vector, which invalidated the pointers.

I'm using vectors and lists & stl containers like that to manage all of my memory - I am really trying to avoid worrying about memory management and low-level issues.

Say Cow is a subclass of Entity, and I have a vector of Cows (or some data structure of Cows). This vector might move itself around in memory if it needs to resize or something. I don't really want to care about those details, but I know they might invalidate my pointers. So if a Pig has a relationship with a cow and the cow vector is resized, the Pig loses track of where its friend is located in memory & has a dangerous incorrect pointer.

Here are my questions, although I think someone experienced might be able to help me more just by reading the situation...

  1. Should I try to slip something into the constructor/destructor of my Entity objects so that they automatically invalidate relationships other entities have with them when they are deleted or update memory locations if they are moved? If this is a good approach, any help on the semantics would be useful. I have been writing all of my classes with no constructors or destructors at all so that they are extremely easy to use in things like vectors.
  2. Is the better option to just keep track of the containers & manually update the pointers whenever I know the containers are going to move? So the strategy there would be to iterate through any container that may have moved right after it moves and update all of the pointers immediately.
user3281410
  • 502
  • 3
  • 14
  • 3
    You might be interested in reading this: http://stackoverflow.com/questions/23620533/thread-safe-vector/23620696#23620696 The author of the question had the same misconceptions as you state more or less. – πάντα ῥεῖ May 15 '14 at 16:27
  • I'll read this. Just looking at it for 10 seconds though, I think I should have said that I haven't been using vectors of pointers. I've been declaring objects and copying them into vectors & letting the vectors manage the memory. I really, really want to avoid having to manually new and delete things, which is why I'm doing it that way. Maybe what I want is something like a vector of unique pointers, if that's even possible? – user3281410 May 15 '14 at 16:33
  • Having pointers into changing containers seems like an odd thing to do. Perhaps you could consider a container where insertion and deletion doesn't invalidate iterators (`map`, `set`, and `list` are probably okay for this) and keep track of your objects using iterators instead? – Rook May 15 '14 at 16:35
  • @Rook Maybe that's right. I have been using vectors because I very frequently need to iterate thru the objects and call update functions of various kinds. I read that it is somehow slower to iterate through lists or sets because of caching issues. – user3281410 May 15 '14 at 16:40
  • 2
    @user3281410 In addition to Rook's explanations, storing object instances managed with smart pointers, sounds like a more appropriate solution for your case. – πάντα ῥεῖ May 15 '14 at 16:44

3 Answers3

3

As I understand it, you're doing something like this:

cow florence, buttercup;
std::vector<cow> v = { florence, buttercup };
cow* buttercup_ptr = &v[1];

// do something involving the cow*

And your buttercup_ptr is getting invalidated because, eg. florence was removed from the vector.

It is possible to resolve this by using smart pointers, like this:

std::shared_ptr<cow> florence = std::make_shared<cow>();
std::vector<std::shared_ptr<cow>> v;
v.push_back(florence);

You can now freely share florence... regardles off how the vector gets shuffled around, it remains valid.

If you want to let florence be destroyed when you pop her out of the vector, that presents a minor problem: anyone holding a copy of the shared_ptr will prevent this particular cow instance being cleaned up. You can avoid that by using a weak_ptr.

std::weak_ptr<cow> florence_ref = florence;

In order to use a weak_ptr you call lock() on it to convert it to a full shared_ptr. If the underlying cow instance was already destroyed because it had no strong references, an excetion is throw (you can also call expired() to check this before lock(), of course!)

The other possibility (as I suggested in my comment on the original question) is to use iterators pointing into the container holding the cow instances. This is a little stranger, but iterators are quite like pointers in many ways. You just have to be careful in your choice of container, to ensure your iterators aren't going to be invalidate (which mean syou can't use a vector for this purpose);

std::set<cow> s;
cow florence;
auto iter = s.insert(florence);

// do something with iter, and not worry if other cows are added to
// or removed from s

I don't think I'd necessarily advise this option, though I'm sure it would work. Using smart pointers is a better way to show future maintainance programmers your intent and has the advantage that getting dangling references is much harder.

Rook
  • 5,734
  • 3
  • 34
  • 43
  • 1
    +1 for mentioning the management using smart pointers. I believe this will provide at least the most appropriate solution for the OP's use case. – πάντα ῥεῖ May 15 '14 at 16:45
  • @Rook I'm still processing what you wrote. I was typing a question about what would happen when I deleted a cow from the vector, and then I refreshed the page and you had already edited your post, psychically lol. Thanks for the help! – user3281410 May 15 '14 at 16:48
  • @πάνταῥεῖ: Not sure about this. I think he should first try to understand pointers better. Chances are that using smart pointers here would shadow the real issues. – Christian Hackl May 15 '14 at 16:48
  • 1
    @ChristianHackl Read my other comments on this question, and check the link I gave. Usage of smart pointers doesn't shadow anything, and is perfectly recommendable for this use case. – πάντα ῥεῖ May 15 '14 at 16:51
  • 3
    Babe's like Buttercup, Girl, where did Florence go, she was right in front of you. Buttercup's like, fool she never left you, you pushed her clone before me. You lost her, guess you should've put a pointer on her. – Arun R May 15 '14 at 16:57
  • I think using iterators will cause exactly the same problems that using pointers does. – Dan May 15 '14 at 16:58
  • @Dan they're not quite exactly the same. For one, it is quite a lot harder to get raw pointers for the location of objects within a `set` or `map`, and even if you could those pointers will be invalidated by container changes whereas the iterators will not be. But I would not recommend using iterators this way, and I said as much, and have a better means of solving the problem before mentioning them. – Rook May 15 '14 at 17:12
  • @Rook I think the weak_ptr thing seems the most appealing to me. Would you mind commenting on the following though. When I want to kill a cow, all of the weak pointers pointing to it will presumably be able to tell its wife that it is no longer in existence. Would it be more reasonable for the husband to try to inform the wife when he dies so that she can get rid of her pointers, or should the wife just check to see if the pointer is valid each time she goes to use it. If this depends on the situation, that's fine too. Thanks. I think I'll accept you answer after reading the others. – user3281410 May 15 '14 at 17:13
  • 1
    @user3281410 You can indeed use something like the [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) to motify other objects when you're about to do something (like die). Only you know whether those other objects _need_ to know when a `cow` is being destroyed, or whether having them find out via `weak_ptr.expired` is adequate. The latter requires a lot less coding, and is a lot easier as a result! – Rook May 15 '14 at 17:16
2

You have a misconception about pointers. A pointer is (for the sake of this discussion!) just an address. Why would copying an address invalidate anything? Let's say your object is created in memory at address 12345. new therefore gives you a pointer whose value is 12345:

Entity *entity = new Cow; // object created at address 12345
// entity now has value 12345

Later, when you copy that pointer into a vector (or any other standard container), you copy nothing but the value 12345.

std::vector<Entity *> entities;
entities.push_back(entity);
// entities[0] now has value 12345, entity does not change and remains 12345

In fact, there's no difference with regards to this compared to, say, a std::vector<int>:

int i = 12345;
std::vector<int> ints;
ints.push_back(i);
// ints[0] now has value 12345, i does not change and remains 12345

When the vector is copied, the values inside do not change, either:

std::vector<int> ints2 = ints;
std::vector<Entity *> entities2 = entities;
// i, ints[0] and ints[0] are 12345
// entity, entities[0] and entities[0] are 12345

So if a Pig has a relationship with a cow and the cow vector is resized, the Pig loses track of where its friend is located in memory & has a dangerous incorrect pointer.

This fear is unfounded. The Pig does not lose track of its friend.

The dangerous thing is keeping a pointer to a deleted object:

delete entity;
entities[0]->doSometing(); // undefined behaviour, may crash

How to solve this problem is subject of thousands of questions, websites, blogs and books in C++. Smart pointers may help you, but you should first truly understand the concept of ownership.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • 2
    I don't believe the OP has a `std::vector`... they are instead acquiring pointers into a `std::vector`, and those pointers are getting invalidated when the vector is changed. – Rook May 15 '14 at 16:48
  • 1
    @Rook: really? I would not have guessed that from his question. But you may be right. Perhaps the OP should have posted some example code. – Christian Hackl May 15 '14 at 16:49
  • 1
    @ Christian Hackl Sorry Christian, I should have mentioned that in the main body of my post. I added it in a comment. It's hard to strike a balance between writing a wall of text that nobody wants to read and giving enough details. Thanks for the post nonetheless. I think I have a handle on regular pointers, I just prefer to avoid them because they make things hard to keep track of. – user3281410 May 15 '14 at 17:16
2

My two cent. The code below is a working example (C++11, translates with g++ 4.8.2) with smart pointers and factory function create. Should be quite failsafe. (You can always destroy something in C++ if you really want to.)

#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

class Entity;


typedef std::shared_ptr<Entity> ptr_t;
typedef std::vector<ptr_t> animals_t;

class Entity {
protected:
    Entity() {};
public:
    template<class E, typename... Arglist>
    friend ptr_t create(Arglist... args) {
        return ptr_t(new E(args...));
    }

    animals_t my_friends;

    virtual std::string what()=0;
};

class Cow : public Entity {
    double m_milk;
    Cow(double milk) :
        Entity(),
        m_milk(milk)
    {}
public:
    friend ptr_t create<Cow>(double);

    std::string what() {
        std::ostringstream os;
        os << "I am a cow.\nMy mother gave " << m_milk << " liters.";
        return os.str();
    }
};

class Pig : public Entity {
    Pig() : Entity() {}
public:
    friend ptr_t create<Pig>();

    std::string what() {
        return "I am a pig.";
    }
};

int main() {
    animals_t animals;

    animals.push_back(create<Cow>(1000.0));
    animals.push_back(create<Pig>());

    animals[0]->my_friends.push_back(animals[1]);

    std::for_each(animals.begin(),animals.end(),[](ptr_t v){
            std::cout << '\n' << v->what();
            std::cout << "\nMy friends are:";
            std::for_each(v->my_friends.begin(),v->my_friends.end(),[](ptr_t f){
                    std::cout << '\n' << f->what();
                });
            std::cout << '\n';
        });
}

/*
    Local Variables:
    compile-command: "g++ -std=c++11 test.cc -o a.exe && ./a.exe"
    End:
 */

By declaring the Entity constructor as protected one forces that derived objects of type Entity must be created through the factory function create.

The factory makes sure that objects always to into smart pointers (in our case std::shared_ptr).

It is written as template to make it re-usable in the subclasses. More precisely it is a variadic template to allow constructors with any numbers of arguments in the derived classes. In the modified version of the program one passes the milk production of the mother cow to the newly created cow as a constructor argument.

Tobias
  • 5,038
  • 1
  • 18
  • 39
  • Addendum: If you're sure that an `Entity` won't get removed while there are other entities referencing it, you can use `unique_ptr`s in the main `vector` and plain pointers to store friends, then you don't have the penalty of the `shared_ptr` object. – Dan May 15 '14 at 17:13
  • I'm trying to understand what's going on in your code. It looks very interesting, but I have no idea what s going on right after the public: keyword in the Entity class. I'll google, but if you know if a resource that explains this I would be appreciative. Thanks – user3281410 May 15 '14 at 17:23
  • I modified the source and added some words about the template stuff. – Tobias May 15 '14 at 18:09