Unique identifier for functions? (c++)

Started by
11 comments, last by suliman 6 years, 4 months ago

Hi

I've built a system for handling gui-popups that the player can answer to. The popup when called sets an "popup id" so i can check for that popup being answered elsewhere in the code like so:


if (timeToEat())
  callPopup(13, "What to eat?", "Cake", "Cookies!");

//elsewhere in code
if (popupAnswered(13,0)) // clicked first button in popup
  eatCake();

if (popupAnswered(13,1)) // clicked second button in popup
  eatCookiesDamnit();

So i just increase the id (in the example it's 13) when adding more popups. This isn't ideal of course but I don't want to add defines or enums to identify the popups. Adding new popups should be easy.

Another idea is to set a string value when calling a popup and check for that string to be exactly the same (the string will be the ID instead). Would that make sense? What else could I do? 

See below for string identifier example:


if (timeToEat())
  callPopup("popup_eat_question", "What to eat?", "Cake", "Cookies!");

//elsewhere in code
if (popupAnswered("popup_eat_question",0))
  eatCake();

if (popupAnswered("popup_eat_question",1))
  eatCookiesDamnit();

 

Advertisement

Use a run time hash function that takes in text and converts it to a 16 or 32bit integer (unsigned) or something.  A Murmur hash could work here I think.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Use callbacks/anonymous methods/lambdas/whatever C++'s modern equivalent is.

C# example since it's easier to demonstrate:


Popup("What to eat?", new string[]{"Cake", "Cookies!"}, x =>
{
	switch (x)
	{
		case 0: eatCake(); break;
		case 1: eatCookiesDammit(); break;
	}
});

 

@Mike2343
But what would be the actual benefit over using my crude string comparison method?

The benefits is that you get unique ids for your popups without having any compile time code struggle. Think of the case that you have a hard night and set an id twice so what would happen then? The hashing method prevents this unless you set two popups have the same name.

I would also suggest like @Nypyren did to use some kind of event-(system) to have your popups listen to. This will make life easier when you intend to trigger/listen to popup answers at other parts of your code

I'm not understanding what the problem is.

You have an arbitrary collection of questions and associated answers, and you want to know whether you should index that collection using a string or an integer?  Simple.  Use a string, it's human-readable and can be assigned meaning, and is more useful during debugging.  Have a struct storing the question and answers, and store it in a std::map keyed by the string index.

Move on to the next problem.

Stephen M. Webb
Professional Free Software Developer

3 hours ago, suliman said:

@Mike2343
But what would be the actual benefit over using my crude string comparison method?

Providing you aren't having a problem in practice with your IDs, there is no problem in using it. In some cases using callbacks / enums / strings etc can be overengineering.

But it totally depends on how many popups you are dealing with, and the use case. If it starts with 3 popups, then you find you are using it more than you first envisaged and you become more likely to make mistakes with ID collisions etc. If there are only a few canned 'actions' in response to the selections, it may make sense to store these on the GUI object and make it 'self aware' rather than deal with lots of similar case statements.

If the number of popups get ‘big’ in your code base, the chances of errors creeping with just hard coded number like 13 will increase massively.  I would use enums or the string method (using maps) instead of hard-coded numbers.  Just imagine doing a global search in your code base for the number ‘13’ compared to ‘popup_eat_question’, to find all uses of your popup.
 

Instead of manually assigning an ID, make the function automatically assign one, and return it. You can then store it in a named (!) variable and use that variable to refer to the popup, instead of obfuscating meaning behind an arbitrary integer literal (which is just an implementation detail).


auto cakePopup = CreatePopup("Cake?", "Ya", "Na");

if (PopupAnswered(cakePopup,1)) DoStuff();

Popups are all named, and relying on variables over literals should enforce proper scoping and dependency passing (currently you can read/write/modify a popup from anywhere at any time knowing just the integer ID - the dependency is passed through the programmer, which is not ideal, more formal/documented approach scales better).

If you want more flexibility, just do what you would do with any other regular object - declare it in a higher scope (even a global if that is what is required to fully replace your current approach, the point is to use the most local scope possible to minimize clutter), or store handles in a map (as in string=>handle type) or other indirection mechanism instead of a named variable (if you want to load the popups from a data file at runtime / let the user create their own / procedurally generate them - all could use something like that).

The actual type of the handle is less critical (use auto, and a using-declaration so you can easily change it in one place), so it becomes easier to turn it into a struct/class if you want to add more functionality (thats why I used "auto" for the type). An integer is fine, but sometimes adding some additional context, debugging data, or member functions is useful (keeping the type ambiguous leaves the door open for such additions).

Enums could be used to index into a map (if you find you need that indirection layer) of these runtime-allocated handles. Dont use enums to directly name specific handle values at compile time - it requires allocating handles at compile time (which is unnecessary, complex, and limiting, if you already have the more flexible runtime allocation as a foundation).

So you have the basic runtime handle allocation (create any number of popups at any time), and then you can have more than one tool for managing and passing those around (variables in various scopes, containers/maps, function parameters...). You can even use your current integer-indexed approach, just create an array/vector/map of handles somewhere (as in PopupAnswered(popupHandles[13])).

o3o

As others have stated it's trivial to code up (few lines of code to generate the hash) and it's cost is done at compile time.  Depending how often you'll be doing the compares between strings it can get CPU costly whereas integer comparisons are nearly free comparatively. Plus you're less likely to accidentally, once your instances of IDs increases, to type the wrong ID.  As noticing you used the wrong string verses hardcoded integer is easier for most humans.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement