helios wrote: |
---|
associate a define or id (index) with the actual stored element, or would it be better to have all
resources be associated with a string? |
I'm not sure what you mean. Can you give a couple examples? |
I think he means one of
a. ID:
std::map<id_type, object_type> objects;
,
b. Index:
std::vector<object_type> objects;
, or
c. String:
std::map<std::string, object_type> objects;
.
Personally, I would go with (a) in almost all cases. (c) is out because any string large enough to give sufficient uniqueness is likely to take too long to parse for most cases, and (b) is out because passing around indexes into your arrays is unsafe because any index becomes invalid if any previous element is removed, or if any new element gets inserted before it. It's worse if you have multiple threads, because then not only can the indexes become invalid in between calls to the array-owning class' interface, but they can become invalid
during calls too. This could lead to very subtle bugs even in only one thread: imagine one function stores something in the array and gets the value 5. In between calls to that function, another function inserts a new element. Now, 5 refers to a different element, and the function that stored it is none the wiser. You can somewhat solve this problem by only allowing operations on the end of the stack, but that costs you a lot of flexibility. Option (a) is the clear winner.
The main downside of (a) is that you can run out of IDs, but if you have a known limit on the number of IDs (e.g. in a game, you would have a limited number of assets that can be loaded which is unlikely to exceed the word size of the platform even on 16-bit platforms (16 bits gives you 65,535 IDs)) then you'll never have that problem, and if you use a big enough data type, you can effectively ignore the possibility of running out of IDs: even 64 bits is enough that, if you generated 1 billion IDs per second, it would take you almost 300 years to run out. It has the advantages of using unique strings of equal length without the massive performance hit of generating and parsing them because you can do something as simple as just incrementing a counter and returning the result every time you need to generate a new ID. The more uniqueness you need, the larger integers you can use, although it gets complicated if you want to use more than 64 bits because the standard only guarantees up to 64-bits, any more than that is implementation defined (e.g. std::uint_least64_t could be 128-bits wide on platform x, but as there's no std::uint128_t, you couldn't rely on the extra bits unless you were writing unportable code). There is a performance hit involved with using increasingly large integer types, but it's not as large as if you were using strings, and it's exactly the same performance hit you'd have if you used indexes without any of the inflexibility. Plus, if the word size of platforms increases, the performance hit will decrease significantly (on a 64-bit platform, using 64-bit integers is as fast as using 32-bit ones on a 32-bit platform). The only remaining caveat is that if you want to use or allow multiple threads, you have to make sure your ID generator can't generate the same ID twice. I suppose you could use a mutex but that may introduce other problems (race conditions) and it wouldn't make the function re-entrant.
Here's an example of option (a):
http://www.cplusplus.com/forum/lounge/113988/3/#msg626972
Note: That example is
not thread-safe because two threads calling it concurrently could get the same ID for different resources.