This is an example of how to use gperf with C++ to generate hash tables which can be used to make code easier to read and also run faster. This is a short guide on how to do this for something that is quite a common problem.

On of these simple problems comes down to code clarity and performance. If you have ever seen large functions that spam 1000's or line of if or case statements then you will know what the problem is already. Though typically this problem is
formed in client / server driven software that is processing commands on the server. It is caused because the server needs to figure out what function to run from an incoming command from the client. Quite often this ends up in code something like this.

if (command == "ONE") { DoOne(); }
if (command == "TWO") { DoTwo(); }
if (command == "THREE") { DoThree(); }

Typically in a large client / server project the above can grow to 1000's of commands. Quite often new commands are always added to the end of the list. The obvious problem here is that for the 1000'th command it has to evaluate 1000 if statements. This obviously isn't a great solution.

So here is a better way to do it. We can start with some example code that might exist on the server. eg The functions that are being run as the incoming commands are being processed. This is of course a simple example.

class Test {
public:
    static void One() { printf("One\n"); }
    static void Two() { printf("Two\n"); }
    static void Three() { printf("Three\n"); }
};

We also need to create a method of calling this so that the functions above can be called at design time. We can do that by creating a structure with a name and a pointer to the function.

struct TType {
    const char *name;
    void (*func) (void);
};

The next idea is to create a function that can lookup the TType structure in a dataset by the incoming command name. This is where gperf comes in and we can create a gperf file which will end up looking something like the following. There is a working example of a gperf file at the end of this post. Something that is also worth pointing out that in the data list between the %% and %% lines you are not permitted to use spaces as these will be considered empty strings. After all they are valid data. However you can add spacing / comments by prefixing with the #

%ignore-case
%language=C++
%define lookup-function-name Lookup
%define class-name Functions
struct TType
%%
####
ONE,    Test::One
TWO,    Test::Two
THREE,  Test::Three
####
%%

The above will tell gperf to ignore case when doing the string matching and it will specify the output to be C++. It will also create a static C++ class and function in the generate output file named Functions::Lookup along with a static structure of the TType which also contains the entire data list. As an example the C++ code that is generated is as followed. However it does also create a number of other items related to the gperf hash calculate that is performed during the lookup. I have only included a small chunk of the file below as the rest of it really isn't human readable.

static const struct TType wordlist[] =
  {
    {""}, {""}, {""},
#line 31 "gperf-example.gperf"
    {"TWO",    Test::Two},
#line 30 "gperf-example.gperf"
    {"ONE",    Test::One},
#line 32 "gperf-example.gperf"
    {"THREE",  Test::Three}
  };

const struct TType *
Functions::Lookup (register const char *str, register unsigned int len)
{
  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
    { 
      register int key = hash (str, len);

      if (key <= MAX_HASH_VALUE && key >= 0)
        { 
          register const char *s = wordlist[key].name;

          if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strcmp (str, s))
            return &wordlist[key];
        }
    }
  return 0;
}
#line 34 "gperf-example.gperf"

The next step in getting this to work is to make the program call the lookup function. This is normally done by creating the above file from the gperf file by running the gperf command like gperf -tCG gperf-example.gperf > gperf-example.h and then including the file into the C++ code where the lookup function will be called from. As an example you end up with a program that looks
like this.

#include "gperf-example.h"

int main(int argc, char **argv) {
    const TType *tmp = Functions::Lookup("One", 3);

    if (tmp == NULL) {
        printf("FAILED\n");
    } else {
        tmp->func();
    }
}

he above is obviously a little easier to maintain in the long run and runs a lot faster that trying to process 100's of if statements. It can also be integrated with the build system so that the file can be produced automatically when updates are made.

Here is a complete example of a gperf configuration. I put this together to show how to the C++ code can be mixed into the gperf file.

%{
/* gperf -tCG gperf-example.gperf > myfile.cpp */
#include <stdio.h>
#include <string.h>

struct TType {
    const char *name;
    void (*func) (void);
};

class Test {
public:
    static void One() { printf("One\n"); }
    static void Two() { printf("Two\n"); }
    static void Three() { printf("Three\n"); }
};

%}

%ignore-case
%language=C++
%define lookup-function-name Lookup
%define class-name Functions
struct TType
%%
####
ONE,    Test::One
TWO,    Test::Two
THREE,  Test::Three
####
%%

And of course a short test program

int main(int argc, char **argv) {
    const TType *tmp = Functions::Lookup("One", 3);

    if (tmp == NULL) {
        printf("FAILED\n");
    } else {
        tmp->func();
    }
}

The above can be processed, compile and run with the following commands.

gperf -tCG gperf-example.gperf > gperf-example.cpp
g++ -Wall gperf-example.cpp -o gperf-example





Last Modified: 12 December 2016

Releated Posts


2017-03-01 - Shooting yourself in the head with threads
2013-08-22 - CPP - Prevent an object by being copied with boost::noncopyable
2013-03-29 - CPP - Why you need a copy constructor
2013-03-27 - CPP -boost::optional
2012-11-06 - C++ - Stringstream example
2012-11-03 - C - How to override malloc / free
2012-09-22 - Linux Programming - Using inotify for detecting file modifications
2012-08-09 - C++ - Check an IP Address is in a IP Mask
2012-06-30 - CPP - Creating A basic python wrapper
2012-06-16 - CPP - Using gperf
2012-05-10 - CPP - Optional function arguments
2012-04-05 - Using gdb to debug a core file
2012-03-19 - CPP - Read / Write std::map to a file.
2012-03-15 - C - Is the stdin a tty
2012-03-08 - C - UDP Socket example
2012-02-20 - C - Get home dir location in linux
2012-02-15 - C - Get current ip address of an interface
2012-01-26 - C - Example of using popen
2011-12-11 - C - Linux get mac address from interface