0

I have simplified the code as much as possible. So I have two class:

class EntityManager
{
    public:
        std::shared_ptr<std::vector<Entity> > getEntities()
        {
            return std::make_shared<std::vector<Entity> >(m_entities);
        }

    private:
        std::vector<Entity> m_entities{};
};

and

class System
{
    public:
        void loadEntities(std::shared_ptr<std::vector<Entity> > entities)
        {
            m_entities = entities;
        }

    private:
        std::shared_ptr<std::vector<Entity> > m_entities;
};

Now basically I want the m_entities of System to point to the m_entities of EntityManager.

I did this:

system = System();
system.loadEntities(m_entityManager.getEntities());

But then I pushed back an element into the m_entities vector of EntityManager and this element wasn't added in the m_entities vector of System, which means my pointer doesn't point.

Where is my mistake?

Thanks!

  • 1
    Simple code is good but it still must be complete. Read again about how to construct a [MCVE]. – Lightness Races in Orbit Jul 06 '16 at 18:33
  • 3
    Don't think you need a shared pointer there. `EntityManager` is the obvious owner of the entity list and it sure looks like `System` owns the `EntityManager`. All of the ownership and lifetimes should be known. – user4581301 Jul 06 '16 at 18:36
  • What do you mean by should be known? And what do you suggest to use? –  Jul 06 '16 at 18:40
  • 1
    `System` contains an `EntityManager`. `EntityManager` contains a `vector` of `Entity`. This `vector` cannot be destroyed before the `EntityManager` and the `EntityManager` cannot be destroyed before `System`. Ergo the lifespan of all objects is known and nothing is destroyed before `System`is destroyed, so you have no need to fear `System` using raw pointers to the `vector`. The pointer cannot be invalidated before the user is finished with it. That said, add a `getEntity` function to `EntityManager` and there is no need for `System` to even know there is a `vector`. – user4581301 Jul 06 '16 at 18:53
  • 1
    You could make `EntityManager::m_entities` to be a shared pointer initialized via constructor and fixup the rest of the code thereafter, [something like this](http://ideone.com/zcQTmJ), but I'm not certain it will ultimately solve the *real* problem you're set upon. This has a feeling of an [XY problem](http://xyproblem.info/), so think hard about what you're really doing. – WhozCraig Jul 06 '16 at 18:55

3 Answers3

2

Your problem is this line: return std::make_shared<std::vector<Entity> >(m_entities);

What is happening is that the shared_ptr manages a new std::vectory<Entity> container which is initialized as a copy of m_entities. Therefore, modifying the instance in the shared_ptr doesn't modify the data member in the EntityManager class and of course the shared_ptr won't see changes made to EntityManager::m_entities.

James Adkison
  • 9,412
  • 2
  • 29
  • 43
  • Thank you. Is there a way to solve it using a shared_ptr? –  Jul 06 '16 at 18:36
  • @Urefeu Yes, but there is no point. You don't need a shared pointer. – user4581301 Jul 06 '16 at 18:37
  • @Urefeu Why the obsession with shared_ptr here? – Lightness Races in Orbit Jul 06 '16 at 18:37
  • 1
    @Urefeu: You appear to be taking huge sweeping statements and applying them to your code without applying any actual thought or reasoning to your choices. Use `shared_ptr` when it's appropriate, not because somebody told you not to use raw pointers. Why do you need any pointers at all? – Lightness Races in Orbit Jul 06 '16 at 18:40
  • And I need it because that is what I have explained, I want my System's m_entities to be like the EntityManager's one at any time. –  Jul 06 '16 at 18:43
  • @Urefeu See the [answer provided by Lightness Races in Orbit](http://stackoverflow.com/a/38231385/4505712) which states you could make `m_entities` in `EntityManager` a `shared_ptr`. **However, heed the information everyone is giving you.** You don't have to use `shared_ptr` for this, you may not need pointers at all, there are cicumstances where raw pointers are perfectly fine, etc. – James Adkison Jul 06 '16 at 18:43
  • @Urefeu It's unclear what you're doing versus what is required/necessary. Why can't you use normal stack allocated objects and copy semantics? – James Adkison Jul 06 '16 at 18:48
  • I am not familiar with that, I have always done it with pointers (not shared ones though). –  Jul 06 '16 at 18:52
  • @Urefeu A good guideline is to not use dynamic allocation (i.e., `new`) unless you need to. (i.e., prefer stack allocation `int x = 0;` over dynamic allocation `int* x = new int(0);`). – James Adkison Jul 06 '16 at 18:56
  • That's what I try to do, but in order to have the same instance at two different places, what can I do but a reference/pointer? –  Jul 06 '16 at 18:59
  • 1
    Why do you **_need_** the same instance in more than one place as opposed to copying? Why shouldn't the `EntityManager` be copied or shared instead of it's internal data? Also, see [the comment by user4581301](http://stackoverflow.com/questions/38231160/pointer-to-a-vector-doesnt-point/38231224?noredirect=1#comment63884919_38231160). I think all the questions surround _why you think you **need** to do it the way you're trying to do it_ (i.e., maybe you're correct but explain the requirements and constraints in your question). – James Adkison Jul 06 '16 at 19:05
2

std::make_shared doesn't "make this thing shared"; it "makes a thing that will be shared".

So, you can't just make a shared pointer out of nowhere that points to something that already exists.

Your code dynamically allocates a std::vector, copy constructed from m_entities and managed by a std::shared_ptr. It's shorthand for this:

std::vector<Entity>* ptr_to_copy = new std::vector<Entity>(m_entities);
return std::shared_ptr(ptr_to_copy);

It's not clear what you're trying to do, from the code that (by your own admission) does not achieve that goal. But it seems unlikely that std::shared_ptr is appropriate here.

If it is, then make the vector dynamically-allocated and shared from the start; otherwise, just return a reference to the vector as it is.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • I'm a bit lost with the use of different pointers then. When should I use raw, shared, unique ones... ? –  Jul 06 '16 at 18:44
  • @Urefeu Research other questions on this same topic and then possibly ask a _new question_. – James Adkison Jul 06 '16 at 18:45
  • @Urefeu Covered here: http://stackoverflow.com/questions/8706192/which-kind-of-pointer-do-i-use-when The names have changed slightly since that post, but the logic still holds. – user4581301 Jul 06 '16 at 18:55
0

Hack example of a pointer-free solution.

#include <string>
#include <iostream>
#include <vector>

//Hack-sample Entity class
class Entity
{
    public:
        Entity(const char * name): m_name(name)
        {

        }
        void print() // this is stupid in real life. Prefer a << overload
        {
            std::cout << "Hi! I'm " << m_name << "!\n"; 
        }
    private:
        std::string m_name;
};

class EntityManager
{
    private:
        std::vector<Entity> m_entities;

    public:
        // hide the fact that a vector is being used to store the entities.
        // you can now swap out the vector for most standard containers without
        // changing any code other than the using and the declaration of m_entities
        using iterator = std::vector<Entity>::iterator;

        EntityManager(): m_entities({"bob", "bill"}) 
                         // just pre-loading a few test entities
        {
            // RAII says you should load the entities from their source here
        }
        // get the first entity. 
        iterator begin()
        {
            return m_entities.begin();
        }
        // get the end of the entity list
        iterator end()
        {
            return m_entities.end();
        }

        // adds an entity
        void addEntity(const Entity & entity)
        {
            m_entities.push_back(entity);
        }

        // removes an entity
        iterator removeEntity(iterator rem)
        {
            return m_entities.erase(rem);
        }

};

class System
{
    public:

        // example method to show System working with EntityManager by printing all of the Entities
        void printEntities()
        {
            for (EntityManager::iterator it = m_entityManager.begin();
                 it != m_entityManager.end();
                 ++it)
            {
                it->print();
            }
        }

        // example method to show System working with EntityManager by adding Entities
        void addMoreEntities()
        {
            m_entityManager.addEntity(Entity("Ted \"Theodore\" Logan"));
            m_entityManager.addEntity(Entity("Excellent!!!"));
        }

    private:
        EntityManager m_entityManager ;
};

// sample test
int main()
{
    System test;

    test.printEntities();
    test.addMoreEntities();
    test.printEntities();

}

THIS HAS BEEN A HACK. THIS HAS ONLY BEEN A HACK.

If you want to do EntityManager right, see Writing your own STL Container for hints. If you want all of the bells and whistles, the job is fairly complicated. Depending on how you are using EntityManager and the complexity of the Entity management logic, you may be better off discarding EntityManager and just using the plain, old std::vector.

Addendum: What is meant by Resource Acquisition is Initialization (RAII)?

Community
  • 1
  • 1
user4581301
  • 33,082
  • 7
  • 33
  • 54