views:

81

answers:

5

Hi,

I am stuck with a design problem that I hope you guys can help with. I have a few different classes that have various parameters (more than 20 in each case, and they are mostly different, although some are exactly the same in which case they inherit from a base class). I want to control these parameters through the user of a class called ObjectProperties. Each class will create ObjectProperties and populate it with it's own parameter list when the class is initialized. This is done with a map which is set up like so:

std::map<std::string, [data_type]> // where [data_type] can be an int, float, bool, string etc.

the 'string' is the name for the parameter and the data type is the type it's associated with. This is done for easy scripting later on (instead of having 20+ getters/setters). I have already made the ObjectProperties class, but it is less than ideal. I have a structure with all the possible data types called DataTypeStruct and a variable for the map in the ObjectProperties class called pDataTypeMap;

typedef struct
{
   int* IntValue;
   float* FloatValue;
   bool* BoolValue;
   std::string* StringValue;
}DataTypeStruct;

std::map<std::string, DataTypeStruct> pDataTypeMap;

When a class wants to add a parameter for manipulation, it calls one of the following functions, which then creates a new structure appropriately and pairs it with the name to be put in the map. The function returns true or false depending on whether it was able to insert it or not (if false, then the name already exists)

bool AddParam(std::string aName, int* aParam);
bool AddParam(std::string aName, float* aParam);
bool AddParam(std::string aName, bool* aParam);
bool AddParam(std::string aName, std::string* aString);

The reason aParam is/are pointers is because it is required that the parameters must be passed by reference so that they can be manipulated later on.

To change the value for the parameter I have similar functions. As you can see, this is less than ideal, as I have wasted some space with the structure (where each new structure only stores an int OR a bool OR a string OR a float), and calling overloaded functions is unsafe (I 'can' make the functions have unique names, but again, that is less than ideal).

I hope I have explained the design issue that I have come across (it is a little difficult to explain) and would really like to hear suggestions on how to go about solving this problem.

A: 

To reduce your memory usage, use a union instead of a struct for DataTypeStruct. Nothing else in your code would have to change (accessing members is the same as a struct). Now, your structure will only require as much memory as the largest element in it (possibly give or take a few bytes for padding/alignment).

You can also use a generic pointer in your function and cast it to the appropriate type as needed. For example:

enum data_type {INT, FLOAT, BOOL, STRING};
bool AddParam(std::string aName, void* pData, enum data_type type);

Now you have one function to handle all types. Use the value of type to determine how to re-cast the pointer inside the function.

bta
a union cannot have std:string :(
Samaursa
@Samaursa- The union is a union of pointers; it should be able to hold a `std::string*`.
bta
I just saw this reply, and you are absolutely right. I have used your suggestion for another similar problem.
Samaursa
+1  A: 

It seems like it's be pretty easy to just have 4 maps instead of 1 with 4 sets of data. Then, your AddParam() methods would choose the right map based on overloading and the same for your Gets.

You have to worry about them storing multiple items with the same name but different types. You could either iterate through all 4 maps or keep a separate Set with the names of the items in the whole object.

Dave
That is my current implementation, unfortunately, the problem with it is I have to search all four maps (or more, when more types are added) to find the correct value from the given key unless I make four GetParam functions (which I actually did make), e.g. GetParamInt, GetParamFloat etc. Too much bloat and not great when it comes to preparing it for scripting later on.
Samaursa
A: 

A suggestion here is to change DataTypeStruct to

typedef struct
{
   void* value;
   int type; 
   //e.g. 
   //0 for empty,1 for int*,2 for float*,
   //3 for bool*,4 for string* etc.
}DataTypeStruct;

You can then have a single AddParam function

bool AddParam(std::string aName, void* aParam, int type);

You call the function as

int a;
float b;
bool c;
std::string d;

AddParam("test1", (void*)a, 1);
AddParam("test2", (void*)b, 2);
AddParam("test3", (void*)c, 3);
AddParam("test4", (void*)d, 4);

To get the value you can have a function look like the following

void* GetParam(std::string aName);

And used it by calling

int a_return = *((int*)GetParam("test1"));
float b_return = *((float*)GetParam("test2"));
bool c_return = *((bool*)GetParam("test3"));
std::string d_return = *((std::string*)GetParam("test3"));

Now everything look clean, extensible for new types and you save your wasted space :)

Happy programming!

ttchong
I had thought of using void* and have used it for some functionality in the class but because it is unsafe and can have hard to catch runtime bugs, I wanted to avoid it. In fact, my AddParam functions in my current implementation call the function:bool AddParam(std::string aName, void* aParam, DataType aDataType);where DataType is an enum for the supported data types. The main problem with this was the GetParam, oin which case I would have had to use void* and I wanted to avoid that simply because it is error prone.
Samaursa
+4  A: 

I would suggest first taking a look at boost::any, boost:variant, boost::tuple or boost::fusion::vector as the 'vehicles' for your [data_type].

Eugen Constantin Dinca
Prefer `variant` over `any` if possible - `any` is rarely worth the trouble.
Georg Fritzsche
Thanks, that is what I was looking for. I have not used boost very much and did not know boost:variant/any even existed!
Samaursa
+2  A: 

Looks like you're trying to reinvent boost::any.

Mark Ransom