devxlogo

Simplify Callback Dispatching with Enumerated Indexes

Simplify Callback Dispatching with Enumerated Indexes

Callbacks, either in the form of function pointers or pointers to members, offer a significant advantage over direct function calls: the caller doesn’t have to know a function’s name (nor its object’s name, if it’s a member function) in order to invoke it. Alas, this anonymity is a fertile source of human errors as programmers might lose track of which pointer is bound to which function. This solution demonstrates a simple and effective technique to overcome this problem.


How to associate meaningful names to indexes of an array of callback pointers?


Use enum types to enumerate array indexes.Demonstrating the Problem
Suppose you’re developing a graphical mail client that displays “Send,” “Reply,” etc. icons. A typical design consists of a GUI layer (which I will not discuss here) and an engine that intercepts a click on an icon and dispatches a callback function to perform the requested operation. The callback functions are typically stored in an array of pointers to member functions. For example:

class Mail //a set of operations associated with icons{public: int Send(); int Reply(); int Forward(); int Delete();};

To avoid the unwieldy syntax of pointers to members, I will use the following typedef:

typedef int (Mail::*mail_pmf)();

Finally, the array of pointers to members is defined and initialized like this:

mail_pmf options[4]= {&Mail::Send,                       &Mail::Reply,                       &Mail::Forward,                       &Mail::Delete};

When the user clicks on the “Send” icon, the event-dispatcher launches the callback function stored in options[0]; clicking on “Reply” causes the dispatcher to launch options[1] and so on:

enum Icons{ IC_Send    = 10,  IC_Reply	  = 20,  IC_Forward = 30, IC_Delete  = 40, IC_Help    = 50};Mail mail; //object used by dispatcherswitch (icon_val){case IC_Send:  dispatch(mail, options[0]);  break;case IC_Reply:  dispatch(mail, options[1]);  break;case IC_Forward:  dispatch(mail, options[2]);  break;//..}

Even in such a simple example, the use of numeric indexes is a recipe for human errors and maintenance problems. Who can guess what options[1] contains? What if the programmer mistakenly places two identical indexes in two different case blocks?

case IC_Forward:  dispatch(mail, options[2]);  break;case IC_Delete:    dispatch(mail, options[2]);//oops! should be options[3]  break;

Detecting such bugs (which are common in IDEs that support copy and paste) isn’t always easy. Furthermore, it makes code maintenance difficult. What if you decide to add more operations such as “Save” and “Print”?

Obviously, forcing programmers to remember by heart which array element is associated with which operation is a bad idea.

Enumerating Options
Using an enum for binding meaningful names to numeric values is an ideal solution to this problem. However, it has to be done carefully. The first step is to define an enumeration that lists all the possible operations:

enum mail_ops{ send, reply, forward, delete};

Remember to leave all enumerators without an explicit initializer. This way, the compiler automatically assigns consecutive numbers to each enumerator starting with 0, i.e., send=0, reply=1 etc. Secondly, provide a final enumerator after all the valid operations:

enum mail_ops  { send, reply, forward, delete, max_mail_ops //always should be the last };
Author’s Note: I deliberately defined an independent enum for the operations instead of reusing enum Icons because there are icons for which class Mail doesn’t define an operation, “Help” for instance. Furthermore, mail_ops adheres to stricter definition rules. Therefore, it’s best to use separate enums for the icons and the operations.

The last enumerator contains the total number of operations, which is handy for declaring an array of callbacks:

mail_pmf options[max_mail_ops]= {&Mail::Send,                                  &Mail::Reply,                                  &Mail::Forward,                                  &Mail::Delete};

This makes your code more resilient to changes. If, for example, you decide to add more operations:

enum mail_ops{ send, reply, forward, delete, save, //newly added  max_mail_ops  };

max_mail_options will be adjusted automatically to 5, as would the number of elements in the array. Of course, the new elements must be initialized appropriately:

mail_pmf options[max_mail_ops]= {&Mail::Send,                                  &Mail::Reply,                                  &Mail::Forward,                                  &Mail::Delete,                                 &Mail::Save};

enums Will Save the Day
The most important contribution of this technique is in the event-dispatcher. Instead of using meaningless numbers as array indexes, you can now use mnemonic enumerators:

switch (icon_code){case IC_Send:  dispatch(mail, options[send]);  break;case IC_Reply:  dispatch(mail, options[reply]);  break;case IC_Forward:  dispatch(mail, options[forward]);  break;//..case IC_Save:  dispatch(mail, options[save]);  break;case IC_Help:  showOnlineHelp();  break;}

As a bonus, detecting typos is much easier now:

case IC_Save:  dispatch(mail, options[save]);  break;case IC_Print:  dispatch(mail, options[save]);//a typo! 

But That’s Not All!
enum types offer two benefits over const int values: they automatically assign consecutive values to enumerators and they are strongly typed. Thus, if you declare a function that returns an enum:

mail_ops getUserClick();

The compiler can easily catch silly mistakes like this one:

mail_ops getUserClick(){ return 20; //error}

Some compilers are even clever enough to warn about missing enumerators in a switch statement.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist