2
\$\begingroup\$

I am making a game similar to Minecraft, and I am trying to fine a way to keep a map of Block objects sorted by their id. This is almost identical to the way that Minecraft does it, in that they declare a bunch of static final Block objects and initialize them, and then the constructor of each block puts a reference of that block into whatever the Java equivalent of a std::map is, so there is a central place to get ids and the Blocks with those ids.

The problem is, that I am making my game in C++, and trying to do the exact same thing. In Block.h, I am declaring the Blocks like so:

//Block.h
public:
    static const Block Vacuum;
    static const Block Test;

And in Block.cpp I am initializing them like so:

//Block.cpp
const Block Block::Vacuum = Block("Vacuum", 0, 0);
const Block Block::Test = Block("Test", 1, 0);

The block constructor looks like this:

Block::Block(std::string name, uint16 id, uint8 tex)
{
//Check for repeat ids
if (IdInUse(id))
{
    fprintf(stderr, "Block id %u is already in use!", (uint32)id);
    throw std::runtime_error("You cannot reuse block ids!");
}
_id = id;
//Check for repeat names
if (NameInUse(name))
{
    fprintf(stderr, "Block name %s is already in use!", name);
    throw std::runtime_error("You cannot reuse block names!");
}
_name = name;

_tex = tex;
//fprintf(stdout, "Using texture %u\n", _tex);

_transparent = false;
_solidity = 1.0f;

idMap[id] = this;
nameMap[name] = this;
}

And finally, the maps that I'm using to store references of Blocks in relation to their names and ids are declared as such:

std::map<uint16, Block*> Block::idMap = std::map<uint16, Block*>(); //The map of block ids
std::map<std::string, Block*> Block::nameMap = std::map<std::string, Block*>(); //The map of block names

The problem comes when I try to get the Blocks in the maps using a method called const Block* GetBlock(uint16 id), where the last line is return idMap.at(id);. This line returns a Block with completely random values like _visibility = 0xcccc and such like that, found out through debugging.

So my question is, is there something wrong with the blocks being declared as const obejcts, and then stored at pointers and accessed later on? The reason I cant store them as Block& is because that makes a copy of the Block when it is entered, so the block wouldn't have any of the attributes that could be set afterwards in the constructor of any child class, so I think I need to store them as a pointer.

Any help is greatly appreciated, as I don't fully understand pointers yet. Just ask if you need to see any other parts of the code.

\$\endgroup\$
  • \$\begingroup\$ anything else other _visibility with bad values is returned or all the other values are ok ?? \$\endgroup\$ – concept3d Oct 31 '13 at 22:41
  • \$\begingroup\$ There's nothing wrong with using const pointers. You can store const pointers in a container, e.g. std::map<uint16, const Block*>. There is something wrong with using std::map (a tree) to access a contiguous list of integer ids instead of using std::vector, though. \$\endgroup\$ – Sean Middleditch Oct 31 '13 at 22:52
  • \$\begingroup\$ Unfortunately I want the IDs to be able to be non-contiguous, so a vector isn't an option without having to iterate over the entire thing whenever I want to get a block (which happens quite often). \$\endgroup\$ – sm81095 Nov 1 '13 at 1:30
  • 2
    \$\begingroup\$ You can find ways to make the lookup contiguous. For instance, two layers of IDs - the internal IDs that are contiguous and external IDs that are then mapped to the internal ones. And you can consider using std::unordered_map instead which -- while being a pretty bad hash map -- is a lot better than a plain std::map unless you actually need ordered keys. A good design here for mods and such is to use a GUID to uniquely identify a block and then contiguous IDs for the actual blocks, with the mapping table saved out to serialized maps. \$\endgroup\$ – Sean Middleditch Nov 1 '13 at 2:25
  • \$\begingroup\$ @SeanM. did you solve your bad values problem ? \$\endgroup\$ – concept3d Nov 1 '13 at 12:38
1
\$\begingroup\$

The reason I cant store them as Block& is because that makes a copy of the Block when it is entered,

Raw pointers should be avoided when possible.

I'm not sure exactly what you mean by 'makes a copy'. Containers themselves can't store raw references. Possibly you are making a function accepting a ref but then putting them into a container with a non-ref.

In C++11 (which I recommend using unless you have compiler restraints) you can also store references in a container with a std::ref or std::cref. You will need to make sure your compiler is compiling in C++11 mode.

#include <iostream>
#include <map>
#include <functional>
using namespace std; 

class Block {
   public:
      Block(const std::string& name, const int block_id) {
         this->name = name;
         this->block_id = block_id;
      }
   private:
      std::string name;
      int block_id;
};

const Block block_1("Vacuum", 0);

int main(int argc, char* argv[]) {
   std::map<int, std::reference_wrapper<const Block>> array;
   array.insert(std::pair<int, reference_wrapper<const Block>>(0, block_1));
}

Otherwise you could make your own simple ref container:

class BlockRef {
   public:
      BlockRef(const Block& blockref): blockref_(blockref) {}
      operator const Block& () const { return this->blockref_; }
   private:
      const Block& blockref_;
};

You could also make a more advanced one with templates that's generic for any type.

Boost has them also if you are ok with using a library.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks a ton! By passing in a BlockRef class and storing them as BlockRefs, all of the variables are properly initialized and the program runs fine. I have only one question: since I have the Block constructor call BlockRegistry::RegisterBlock(BlockRef) on itself, will this still work for child classes of Block? Or will any variables changed in the child constructor not also change in the stored references? \$\endgroup\$ – sm81095 Nov 2 '13 at 5:47
  • \$\begingroup\$ Never mind, answered my own question. This was the nice simple solution that I needed. Many thanks! \$\endgroup\$ – sm81095 Nov 2 '13 at 6:27
2
\$\begingroup\$

First off all To solve your problem, -if that was the only one-, unlike java, C++(MSVC) assigns 0xcccc for uninitialized variables in debug mode, you are not initializing it in the constructor, make sure to initialize all the variables in your class using the initialize list instead of assignment in the constructor, this has two advantage:

  1. It has to do with calling the destructor in a throwable constructor.
  2. It will only call the constructor and not both the constructor and the assignment operator.

    Block::Block(std::string name, uint16 id, uint8 tex):_visibility (0) {

    }

The reason I cant store them as Block& is because that makes a copy of the Block when it is entered

First of all you need to think of references as Object* const x; this means the pointer is const and not the object itself. But the real reason you can't store references in stl containers is that:

  • T must be default constructible (a reference can only be initilized)
  • T must be copyable (a reference cannot be changed after initlization)

Is there something wrong with the blocks being declared as const obejcts, and then stored at pointers and accessed later on?

Nothing wrong, as far as you are sure of the allocated object lifetime; don't delete it from other pointers referencing it.

You may want to create a class that will manage blocks (e.g. factory,manager etc) this will make managing resources easier.

[Edit] This is only a guess: It seems that both your Registry class and your Block object are define static or global. The problem is that C++ does not define the order in which static objects should be allocated. I suggest you create an Init() function that explicitly initialize your Block object by allocating them on the heap.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ So you are saying that the best way to store the Blocks in the map is using a Block*, without it being a const object or a const pointer? And then also to use things like :_transparent(true) for all of the variables? \$\endgroup\$ – sm81095 Nov 1 '13 at 21:24
  • \$\begingroup\$ nope actually what I meant there is a different between const int* and int const ! a reference is int *const; you need to look that up. and there is nothing wrong with storing const Block this means that the Block is const but the pointer is not. \$\endgroup\$ – concept3d Nov 1 '13 at 21:30
  • \$\begingroup\$ regarding :_transparent(true) you should initialize all the variables .. the reason you have 0xCCC is that it was not initialized at all \$\endgroup\$ – concept3d Nov 1 '13 at 21:31
  • \$\begingroup\$ C++ does NOT initilize anything to 0, java does so that was your problem. \$\endgroup\$ – concept3d Nov 1 '13 at 21:39
  • \$\begingroup\$ I changed my code to include a BlockResistry class, and changed the Block constructor to this. However, I am still getting the same error as before, where the Block variables aren't initialized. \$\endgroup\$ – sm81095 Nov 1 '13 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.