0

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?
Community
  • 1
  • 1
kazarey
  • 814
  • 1
  • 15
  • 32
  • 2
    You may get rid of `enable_share_from_this` by making constructor `private`, and provide static constructor which also feed the vector. – Jarod42 Jul 06 '16 at 18:01
  • @Jarod42, I guess you mean static named constructor (or create function), otherwise I agree 100%. – lorro Jul 06 '16 at 18:12

2 Answers2

1

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.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

I would start by moving the static vector outside of your GameObject class, and stay away from putting code that manages the game objects, inside the game object itself. The static create() method proposed by another user would help, but I personally prefer the ability to call the GameObject/Derived game object constructors myself, so that I can pass whatever data I need during construction.

Create something like a GameObjectWorld class, which you would add objects into during the execution of you game engine. There is no need to automatically add them into the world, right from the GameObject constructor.

You could do something like:

auto gameObject = std::make_shared<GameObject>();
// implemented at a singleton for simplicity
GameObjectWorld::getInstance().add(gameObject);

In the past, I've taken the approach where the given game "Part" or "Scene" maintains it's own list of GameObjects (entities), which in turn maintain their own lists. This allows you to perform actions by recursing through them, with the added benefit of an object hierarchy.

Here's a crude and simple example:

class Part
{
    std::vector<std::shared_ptr<GameObject> children;
public:
    void addChild(std::shared_ptr<GameObject> object)
    {
        children.push_back(object);
    }

    // Part lifecycle
    virtual void onCreate() = 0; // must be provided by your parts
};

class GamePart :: public Part
{
    void onCreate() override; // called by your engine
};

void GamePart::onCreate()
{
    addChild(std::make_shared<GameObject>());
}

This is a very simple way to start managing your game objects and I'd highly recommend reading up on "Entity Component Systems" and understanding some of the ways industry experts manage their game worlds.

nenchev
  • 1,998
  • 28
  • 16