0

I have a vector:

std::vector<uint16_t> free_ids;

I need for it operator== of my class GameObject. When an object is created, it will receive free id from vector, thus it will be "moved" from it to object. It will get values simply like this:

void init(void)
{
    for(uint64_t i=0; i<30; ++i)
        free_ids.push_back(i);
}

So I have class that uses that succesfully.

class GameObject
{
    public:
        static std::vector<GameObject*> created_objects;     // all objects created ever
        static constexpr auto& CO = created_objects;

    GameObject()
    {
        id = free_ids.front();               // get id from vector
        free_ids.erase(free_ids.begin());    // delete it from vector
        CO.push_back(this);                  // add address to all object created ever
    }

    GameObject(const GameObject& other)
    {
        // copy attributes I didn't include in this code
        id = free_ids.front();
        free_ids.erase(free_ids.begin());
        CO.push_back(this);
    }

    ~GameObject()
    {
        free_ids.push_back(id); // return id to vector
        CO.erase(std::remove(CO.begin(), CO.end(), this), CO.end()); 
                                       // remove object by address
    }

    bool operator==(const GameObject& other)
    {
        return id==other.id;    // check if id is the same (if it's the same object)
    }

    const uint64_t& get_id(void) const
    {
        return id;
    }

private:
    uint64_t id;
};

std::vector<GameObject*> GameObject::created_objects;

I'd love to have global constant of type GameObject, but it will cause segmentation fault, because init() was never called before main() call

//const GameObject Progenitor; //segmentation fault, free_ids not initialized yet

And a sample program:

int main()
{
    srand(time(NULL));

    init();

    const GameObject Progenitor; // It's temporary replacement for my global

    std::vector<GameObject> clone_army;
    clone_army.reserve(20); // GameObjects can't be reallocated bacause 
                            // their addresses are stored in class static vector
    auto& CA = clone_army;

    for(uint64_t i=0; i<20; ++i)
        CA.push_back(Progenitor);

    std::cout << "Storage used. Unoccupied ids: " << std::endl;
    for(auto& x : free_ids)
        std::cout << x << std::endl;
    auto& victim = clone_army[rand()%20]; // it's much more compilated

    std::cout << "\nOne will die. Victim is... clone no. " << victim.get_id() << std::endl;
    CA.erase(std::remove(CA.begin(), CA.end(), victim), CA.end()); 
                 // need to remove victim by value, operator== involved

    std::cout << "\nProgenitor id: ";

    for(auto& x : GameObject::CO)
        std::cout << x->get_id() << std::endl;
}

Responsible headers:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <ctime>

My question is, how to initialize std::vector<uint16_t> free_ids; - which can't be const, before any object of GameObject class is ever created? (There will be many progenitors of different inherited classes, template-like objects that I will use (already am but want to rearrange code) to create real-time clones)

xinaiz
  • 7,744
  • 6
  • 34
  • 78
  • Make `free_ids` static? – Aesthete Jan 07 '16 at 22:39
  • @Aesthete Just making it static wouldn't ensure safety, I think I'll stick to SergeyA and Christophe advices, GameObjectHelper is exactly what I need. :) – xinaiz Jan 07 '16 at 22:49
  • How is it not safe? You have a static vector of GameObjects already inside the GameObject class. – Aesthete Jan 07 '16 at 22:52
  • @Aesthete not safe in means of initialzation. created_objects is empty be default, and free_ids should be - like - initialized to these values from the loop. which creates problem in many translation units. – xinaiz Jan 07 '16 at 23:03

3 Answers3

2

While it is easy to create a static object which will initialize the vector in it's constructor, you can never guarantee that this static object will be initialized before all other static objects in different translation units.

Instead, what you might do is to employ a singleton-type thing. Within this singleton, you can expose get_id and release_id functions. Judging by the code provided, I do not think you need me to sketch out this singleton for you, but if you do, feel free to request.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Thanks for responding :) I think I'll try Christophe's model first. If it won't suffice, I'll ask. – xinaiz Jan 07 '16 at 22:50
  • @BlackMoses, suit yourself :), but it might do you good to read my commentes for this solution. – SergeyA Jan 07 '16 at 22:52
  • @BlackMoses well, if you chose this option because of SergayA 's comment on multithreading, you'll have to take care as well of concurrent access to your global vector, whether it's singleton or not (see [here](http://stackoverflow.com/questions/9042571/is-stdvector-or-boostvector-thread-safe), why) – Christophe Jan 07 '16 at 23:49
1

To be honest, you could just do this.

class GameObject
{
private:
    using InstanceId = unsigned long long;
    static InstanceId _OBJECT_ID = 0;

protected:
    const InstanceId mId;

public:
    GameObject()
            : mId(_OBJECT_ID++)
    {}
};

Of course you could get conflicts if your game spawns more than 18446744073709551615 objects during a run.

Aesthete
  • 18,622
  • 6
  • 36
  • 45
  • And what if i create 20 objects, and delete the 13th? It will result 11, 12, 14, 15, when I cannot allow. And personally I thinks it's a waste of potential. – xinaiz Jan 07 '16 at 23:07
  • Why can't you allow it? You're only using these ids to compare them with other objects. – Aesthete Jan 07 '16 at 23:07
  • I use it for overall statistics, well, it's complicated, but the original code is way too huge to post it here. Thanks for concept though :) – xinaiz Jan 07 '16 at 23:10
0

Easy option:

You could define a helper class, that make sure that your vecotr is initialized only once:

class GameObjectHelper {
    static bool done; 
public:
    GameObjectHelper() {
        if (!done) {
            init(); 
            done = false; 
        }
    } 
}; 
bool GameObjectHelper::done = false; 

Then you can make sure that the constructor of this object is called before the constructor of GameObject by taking benefit of the fact that a base class is constructed before its derived:

class GameObject : private GameObjectHelper { 
    ...
 } ;

Important edit: If your GameObjects are multithreaded, done will be initialized to false only once in a thread safe fashion. Unfortunately, several threads could enter the if statement and lead to a race condition. I didn't address this topic in first place, because your remaining code doesn't show evidence of multithreading: your access to the global vector is not thread safe and might lead to adverse race conditions. If you really need multithreading, and if you can't apply the second option, then you should use atomic done with compare_exchange_strong() to test its value.

Cleaner variant:

If you want to avoid your vector being global, you could as well define a helper class such as:

class GameObjectHelper {
    vector<uint16_t> free_ids;
public:
    GameObjectHelper() {
        for(uint64_t i=0; i<30; ++i)
            free_ids.push_back(i);
        }
    } 
}; 

and create a static member obect in the GameObject class:

class GameObject  { 
protected:
    static GameObjectHelper goh; 
    ...
 } ;

Of course this works if and only if your vector is solely used by GameObject and its derivates.

But this one is threadsafe, as there the static object is guaranteed to be initialized only once.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    Will fail spectacularly with multithreading. It is also, strictly speaking, undefined behaviour. There is no guarantee `done` will be initialized before the ObjectHelper constructor will be called. It will be, on all the platforms I know of, simply by the nature of how this sort of initialization is implemented, but C++ standard makes no such guarantees. – SergeyA Jan 07 '16 at 22:46
  • Well, if that would fail in further development, I will avoid using this then. I try to make very secure code myself. It's would be safe, but obscure, to set id to special value in constructor, and set propher id when calling get_id() method, and then flag it. Destructor would have also to be redesigned. – xinaiz Jan 07 '16 at 23:01
  • If It's multithreaded since the initialisation, the problem is that we should use an `atomic` `done`, and that it has to be tested and set in an atomic fashion, i.e. with a `test_and_set()` or a `compare_exchange_strong()` – Christophe Jan 07 '16 at 23:04
  • Note also that if there is no guanrantee that done will be initialized before the object helper constructor is called, it is gauanteed to be initialised at latest when the constructor body is started and only once. It is also guaranteed to be zero initialized BEFORE ANY OTHER initialisation takes place (please rerfer to std 3.6.2/2) – Christophe Jan 07 '16 at 23:17