kazarey kazarey - 3 months ago 16
C++ Question

How do I track objects of a class using smart pointers?

I am trying to write a class whose objects are aware of each other (i.e. have a pointer to all the objects). I can't understand some aspects of implementation of this idea regarding smart pointers and static members.

The class can be thought of as a game object that needs to be able to access member functions and properties of other game objects.

To my best knowledge, the common way to implement the desired design is static vector which would contain the pointers to other objects. If operating by raw pointers, the task is not very complicated:

GameObject.h:

#pragma once

#include <vector>

class GameObject
{
private:
static std::vector< GameObject* > objects;
public:
GameObject()
{
objects.push_back(this);
}
};


GameObject.cpp:

#include "GameObject.h"

std::vector< GameObject* > GameObject::objects = {};


This would actually give what I need. But if I want to use smart pointers, things are not as straightforward to me. From this question and from the 'Effective Modern C++' book by Meyers I found out about
std::enable_shared_from_this
and
shared_from_this()
. But, additionally, the reference clearly states that
shared_from_this()
is allowed to be used only in case the object already owned by a
std::shared_ptr<>
.

So it is impossible to simply push into the static vector
this
pointers (or
std::shared_ptr
constructed over it) in the constructor in the same manner as previously. The minimum set of code allowing the design which I found out is the following:

GameObject.h:

#pragma once

#include <vector>
#include <memory>


class GameObject : public std::enable_shared_from_this<GameObject>
{
private:
static std::vector< std::shared_ptr<GameObject> > objects;
public:
GameObject() {}

void emplace_ptr()
{
objects.emplace_back(shared_from_this());
}
};


GameObject.cpp:

#include "GameObject.h"

std::vector< std::shared_ptr<GameObject> > GameObject::objects = {};


main.cpp:

#include "GameObject.h"

int main(int argc, char* argv[])
{

std::shared_ptr<GameObject> game_object{ new GameObject{} };
game_object->emplace_ptr();
return 0;
}


So I am apparently obliged to create a pointer to the object somewhere outside and then explicitly call a method to push the pointer to the static vector (as I am not allowed to do this in constructor).

I am getting the impression that the required code is getting unnecessarily complex (comparing the raw pointer case) or I am doing something nonsensical.


  1. Does my strive for making object aware of each other make sense
    at all? Is it a common problem or some other approach is usually
    taken?

  2. Are static vectors a sound solution to the problem?

  3. How to construct such vectors using smart pointers, but, preferably,
    with no intervention from the outside and with no need to create the
    special function
    emplace_ptr()
    for the purpose?


Answer

You don't need enable_shared_from_this, instead you could have a static factory function which create the (shared) instance, puts it in the vector, and also returns it.

Something like

class GameObject
{
private:
    static std::vector<std::shared_ptr<GameObject>> objects;

    // Don't allow creation from outside
    GameObject() {}

public:
    static std::shared_ptr<GameObject> create()
    {
        objects.emplace_back(new GameObject);
        return objects.back();
    }
};

Then to get an instance you do e.g.

auto new_game_object = GameObject::create();

There is just one problem with this: The object pointed to by the shared pointer will never go out of scope as long as they are in the vector, and the lifetime of the vector is the lifetime of the program (because it's static). So you have to think of where and when to remove those instances from the vector.