0

Presume this situation:

  • Max Number of 256 key slots.
  • Key slots are defined by a struct, and a variable for each (256) has to exist.
  • User defines which keys slots are active at initiation (in my example, I just coded them in). They however will remain constant throughout execution.
  • Program receives input, sends it to slot, and performs user defined action.

Both samples of code presume this struct:

struct Key;
QList<Key*> keyList;
struct Key {
    const uint_fast8_t keyNumber; // Unsigned 8 bit integer
    const QString      action;

    /* Constructor - Will add self to keyList if cmd is specified */
    Key(const uint_fast8_t keyNum, const QString cmd="")
        : keyNumber(keyNum),
          action(cmd)
    {
        if (!cmd.isEmpty) { keyList.append(this); }
    }
};

int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        uint_fast8_t inputKey;

        /* All variables have to exist. */
        Key _001(1);
        Key _002(2);
        Key _003(3,  "boat programming"); // Automatically added to keyList
        Key _004(4);
        ...
        Key _075(75);
        Key _076(76, "foo bar");
        Key _077(77);
        ...
        Key _254(254);
        Key _255(255,"take yee flask");

        /* etc... */

Solution 1: A giant switch

switch (inputKey) {
    case _001.number: execute(_001.action); break;
    case _002.number: execute(_002.action); break;
    case _003.number: execute(_003.action); break;
    ... 
    ... 
    ... 
    case _255.number: execute(_255.action); break;
    case _256.number: execute(_256.action); break;
    case default: break;
}

Issue: Inefficient if only three actions are defined

Solution 2: A for loop on a list generated at initiation.

for (int i=0; i<keyList.length; i++) {
    if (inputKey == keyList.at(i).keyNumber) {
        execute(keyList.at(i).action);
        break;
    }
}

Issue: Progressively less efficient compared to switch statement as more items are added.

Solution 3: Is this possible?

switch (inputKey) {
    case _003.number: execute(_003.action); break;
    case _076.number: execute(_076.action); break;
    case _255.number: execute(_255.action); break;
    case default: break;
}

Am I right on the track, or should I be approaching this situation differently?

Obviously my goal is to have the optimal performance within obvious discretion:

enter image description here

  • Possible duplicate of Why use an OO approach instead of a giant "switch" statement? – gnat Jul 18 '17 at 13:43
  • @gnat the first sentence in the first answer He is probably an old C hacker and yes, he talking out of his ass. .Net is not C++; -- if that is relevant, how is this c++ question supposed to be relevant to answers given there? – Akiva Jul 18 '17 at 13:56
  • Ughhh, I just realized that I need to close this because the code is positively awful. Switch cases not using constants? This wont even compile. – Akiva Jul 18 '17 at 18:10
  • asmjit is my personal favorite for exploring this type of code generation. However, you will be like the pig in the funny comic you showed if you aren't at all familiar with assembly. Fundamentally you cannot expect things like jump tables to ever be produced from runtime inputs without a JIT, since you need the compiler to have the information only available at compile-time to make such optimizations. That said, given the data types you're using, I really don't think that trying to optimize the branching here is a priority. – user204677 Dec 1 '17 at 1:02
5

In C++11 or better you could consider using a map of lambdas, e.g.

 std::map<unsigned, std::function<void(void)> actmap;

You'll fill it dynamically, e.g.

unsigned num = somenumber();
actmap[num] = [=](void) { /* some code for the lambda */ };

BTW since you have only 256 possible numbers, you might just use the (probably faster) std::array<std::function<void(void),256> actmap;

then, given some num number, use

 actmap[num]();

to run that action.

If you use GCC as your compiler and accepts non-standard extensions consider -alternatively- using computed goto-s (to labels starting some block).

You could also use some JIT compiling meta-programming approach: From the initial data you'll generate (executable) code at runtime using some JIT library like libgccjit or LLVM or asmjit etc. A variant would be to generate some temporary file containing some emitted C++ code, compile it (by forking a compiler) into a plugin, and use dlopen etc... to load that generated plugin. See also this and that.

Regarding performance, try several ways and measure i.e. benchmark them (don't forget to enable optimizations in your compiler), and choose the best. Perhaps the dynamic call overhead (and the closure making) might not be that important.

  • 1
    … and pre-C++11 it's trivial to explicitly implement the command pattern instead of using lambdas, e.g. std::map<unsigned, Handler*> actions where struct Handler { virtual void perform_action() = 0; virtual ~Handler() {} } is an interface that is subclassed for each action. Then to invoke the actions: actions.at(id).perform_action(). – amon Jul 18 '17 at 15:33
  • 1
    You saw that .action is a QString? – Deduplicator Jul 18 '17 at 16:21
  • I will add that my code is impossible considering switches need constants. Good alternative. – Akiva Jul 18 '17 at 18:12
  • 1
    @amon Why are Handler instances better than the lambdas in this situation? The lambdas seem simpler to me. – Frank Hileman Jul 18 '17 at 18:37
  • 1
    @FrankHileman Of course lambdas are better than explicitly implementing a Functor class, and of course std::function is more convenient than creating an interface for the command pattern. The only reasons not to use them is if you can't use C++11 (which my comment was about), if you want stronger typing, if you can't use dynamic memory allocation, if the handler needs additional fields or methods, or if the handler is not copy-constructible. Likely none of those apply here, but I occasionally encounter these problems. – amon Jul 18 '17 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.