How can I improve this code? (C++ enums)
#1 Members - Reputation: 212
Posted 03 October 2011 - 10:47 AM
Enums solve the automatic index assignment problem, but they require type casting to iterate over or use as indices. So what should I do? Here's my current implementation. The problem is that I would have to manually change all the values when adding or removing a secret.
[source lang='cpp']typedef unsigned int Secret;namespace Secrets{ Secret begin(); Secret end(); Secret POTATO(); Secret NONPOISONOUSMUSHROOM(); Secret TM49(); Secret OBVIOUSSECRET(); Secret DELICIOUSCAKE(); Secret BIGDIPPER(); std::string Name(Secret s); ImgHandle Image(Secret s);}[/source]
[source lang='cpp']namespace Secrets{ const Secret ePOTATO = 0; const Secret eMUSHROOM = 1; const Secret eTM49 = 2; const Secret eOBVIOUS = 3; const Secret eCAKE = 4; const Secret eDIPPER = 5; const Secret eNUMSECRETS = 6; Secret begin() {return 0;} Secret end() {return eNUMSECRETS;} Secret POTATO() {return Secret(ePOTATO);} Secret NONPOISONOUSMUSHROOM() {return Secret(eMUSHROOM);} Secret TM49() {return Secret(eTM49);} Secret OBVIOUSSECRET() {return Secret(eOBVIOUS);} Secret DELICIOUSCAKE() {return Secret(eCAKE);} Secret BIGDIPPER() {return Secret(eDIPPER);} std::string Name(Secret s) { switch (s) { case ePOTATO: return "Potato Battery"; case eMUSHROOM: return "Nonpoisonous Mushroom"; case eTM49: return "TM49 (Tri Attack)"; case eOBVIOUS: return "Very Obvious Secret"; case eCAKE: return "Delicious Cake"; case eDIPPER: return "Big Dipper"; default: assert(false); return "INVALID SECRET"; } } ImgHandle Image(Secret s) { switch (s) { case ePOTATO: return ImgHandle(ImgDataStruct("Secrets/potato.png",0,99,0)); case eMUSHROOM: return ImgHandle(ImgDataStruct("Secrets/nonpoisonousmushroom.bmp",255,0,255)); case eTM49: return ImgHandle(ImgDataStruct("Secrets/tm49.bmp",0,255,0)); case eOBVIOUS: return ImgHandle(ImgDataStruct("Secrets/obvioussecret.bmp",0,255,0)); case eCAKE: return ImgHandle(ImgDataStruct("Secrets/deliciouscake.bmp",0,255,0)); case eDIPPER: return ImgHandle(ImgDataStruct("Secrets/bigdipper.bmp",0,255,0)); default: assert(false); return ImgHandle(); } }}[/source]
#3 Members - Reputation: 1874
Posted 03 October 2011 - 10:55 AM
struct Secret {
enum Index {
mushroom,
cake,
dipper,
/* ... */
count
};
Index idx;
string name;
ImgHandle image;
};
Secret theSecrets[Secret::count] = {
{ Secret::mushroom, "Nonpoisonous Mushroom", ImgHandle( ... ) },
{ Secret::cake, "Delicious Cake", ImgHandle( ... ) }
/* ... */
};
First, it seems to make sense to make some sort of object, since all Secrets seem to have an ID, a description, and an ImgHandle. From here, we can make an array for the type of all the secret-types. Adding or removing a secret requires changing the Secret::Index enum, and will emit a compiler error if you forget to update the array. An array can also be iterated over, and the Index enum doesn't require type casting.
However, this approach requires that the IDs of the items in the array match up. A simple sort on program execution or an assertion could enforce this.
Another alternative would be to use an associative container: map<Secret::Index, Secret>
#4 Members - Reputation: 280
Posted 03 October 2011 - 11:09 AM
The same problems are common when designing databases without redundancy.
http://en.wikipedia....e_normalization
My open source DirectX 10/11 graphics engine. https://sites.google.com/site/dawoodoz
"My design pattern is the simplest to understand. Everyone else is just too stupid to understand it."
#7 Members - Reputation: 2773
Posted 03 October 2011 - 01:27 PM
This.If you're indexing by enum then the std::map is the more appropriate solution.
#include <iostream>
#include <map>
#include <string>
enum Secret
{
POTATO,
MUSHROOM,
TM49,
OBVIOUS,
CAKE,
DIPPER,
NUMSECRETS,
};
int
main(int argc, char* argv)
{
std::map<Secret, std::string> mapping = {
{ POTATO, "Potato Battery" },
{ MUSHROOM, "Nonpoisonous Mushroom" },
{ TM49, "TM49 (Tri Attack)" },
{ OBVIOUS, "Very Obvious Secret" },
{ DIPPER, "Big Dipper" },
};
for (auto p = mapping.begin(); p != mapping.end(); ++p)
{
std::cerr << p->first << ": " << p->second << "\n";
}
}
Compiled and tested on Ubuntu 10.04 with g++ 4.5.2 (this is an older compiler does not support the current C++ standard, so no lambdas or for (auto p: mapping) range iteration).
If you replace the std::string as the map value with a struct, you can get both the name and image.
Professional Free Software Developer
#9 Members - Reputation: 767
Posted 03 October 2011 - 03:44 PM
#10 Members - Reputation: 360
Posted 03 October 2011 - 06:43 PM
It seems a bit odd to use a binary tree when an vector/array is basically a perfect hash for enums. Just don't encode meanings into the integer representation of your enums and there's no problem.If you're indexing by enum then the std::map is the more appropriate solution.
The C++ practice of giving enums special integer values is kind of weird. It almost feels like enums are a hack that paper over a hole in the language. Particularly when they're used as flags.
#12 Members - Reputation: 212
Posted 03 October 2011 - 07:49 PM
[source lang='cpp']typedef unsigned int Secret;namespace Secrets{ //Order must be kept in sync with array in secret.cpp enum SecretID : unsigned int {POTATO, NONPOISONOUSMUSHROOM, TM49, OBVIOUSSECRET, DELICIOUSCAKE, BIGDIPPER, NUMSECRETS}; inline Secret begin() {return 0u;} inline Secret end() {return NUMSECRETS;} std::string Name(Secret s); ImgHandle Image(Secret s);}[/source]
[source lang='cpp']namespace Secrets{ struct SecretData{ std::string name; ImgDataStruct imgdata; //Don't convert to handle until runtime }; const SecretData data[NUMSECRETS] = { {"Potato Battery", ImgDataStruct("Secrets/potato.png",0,99,0)}, {"Nonpoisonous Mushroom", ImgDataStruct("Secrets/nonpoisonousmushroom.bmp",255,0,255)}, {"TM49 (Tri Attack)", ImgDataStruct("Secrets/tm49.bmp",0,255,0)}, {"Very Obvious Secret", ImgDataStruct("Secrets/obvioussecret.bmp",0,255,0)}, {"Delicious Cake", ImgDataStruct("Secrets/deliciouscake.bmp",0,255,0)}, {"Big Dipper", ImgDataStruct("Secrets/bigdipper.bmp",0,255,0)} }; std::string Name(Secret s) { assert(s < NUMSECRETS); return data[s].name; } ImgHandle Image(Secret s) { assert(s < NUMSECRETS); return data[s].imgdata; }}[/source]
#13 Moderator* - Reputation: 5402
Posted 03 October 2011 - 07:51 PM
I think its mostly because any enum isn't guaranteed to be zero initialized (I'm sure there's a more technically correct term) or contiguous. While there isn't anything inherently wrong with fastcall's solution, the more general solution is something like a map.I like fastcall22's solution, except I wouldn't store the index as part of the object. Replacing a simple array with an std::map is a pessimization that I can't understand.
#14 Members - Reputation: 212
Posted 03 October 2011 - 07:55 PM
The only case I can imagine where a map would be useful is if the enum values were discontinuous and had a large range, but that is specifically not the case here.
#15 Members - Reputation: 279
Posted 03 October 2011 - 08:21 PM
If not specified the first enumerator has value zero. For the rest, if not specified the value of the enumerator is one more than the previous enumerator.
#16 Members - Reputation: 360
Posted 03 October 2011 - 08:28 PM
In C++, enums are guaranteed to start at zero and count up one by one from there.I think its mostly because any enum isn't guaranteed to be zero initialized (I'm sure there's a more technically correct term) or contiguous. While there isn't anything inherently wrong with fastcall's solution, the more general solution is something like a map.
Without that guarantee, a lot of normal enum usages would break down.
enum PizzaToppings
{
Cheese, Pepperoni, Anchovies, ToppingsCount
};
ToppingsCount will be 3.
#17 Members - Reputation: 5873
Posted 03 October 2011 - 08:28 PM
I think its mostly because any enum isn't guaranteed to be zero initialized (I'm sure there's a more technically correct term) or contiguous. While there isn't anything inherently wrong with fastcall's solution, the more general solution is something like a map.
I like fastcall22's solution, except I wouldn't store the index as part of the object. Replacing a simple array with an std::map is a pessimization that I can't understand.
In C++ an enum is guaranteed to start at 0 and continue with 1, 2, 3, etc.
From the ANSI C++ standard (7.2 - Enumeration declarations [dcl.enum]):
If the first enumerator has no initializer, the value of the corresponding constant is zero. An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.
#18 Moderator* - Reputation: 5402
Posted 03 October 2011 - 08:52 PM
This is safe and well defined in this case. But there may come a time when you're using some external library, and you cant always assume the index will start at zero, because the library's author may have specified a different initialization. I just wanted the OP to be aware of that and know that if he is going to use an enum to index an array, he needs to know exactly how the enum was defined, or else he may end up with a big mess. Plus I was just responding to alvaro's comment, saying that there are indeed times when a map is both preferred and necessary.
Like I said, it is for this reason (and the fact that the programmer may have chosen non contiguous values) that maps are considered the "more general" solution. It does not, however, invalidate fastcall's solution in this case.
#20 Moderator* - Reputation: 5402
Posted 03 October 2011 - 09:28 PM
If you already do know everything I have stated, wonderful. Then I hope what I said will help someone who may browse these forums in the future and hopefully help them avoid making a poor assumption by helping them understand *why* its a poor assumption.
In this particular case, its safe, like I've said before. But its not safe in every possible case. You may already know that, but I have no way of knowing whether you do or not, nor do I know if any potential beginners browsing this thread know that or not. So I tried to add a small, clear comment as to why it isn't safe in every case.






