Best way to store persistent data in C ++

I have an array of persistent data, for example:

enum Language {GERMAN=LANG_DE, ENGLISH=LANG_EN, ...};
struct LanguageName {
    ELanguage language;
    const char *name;
};

const Language[] languages = {
    GERMAN, "German",
    ENGLISH, "English",
    .
    .
    .
};

      

When I have a function that accesses an array and finds an entry based on the language enumeration parameter. Should I write a loop to find a specific entry in the array or are there better ways to do this.

I know that I could add LanguageName objects to the std :: map, but wouldn't that be overkill for such a simple problem? I don't have an object to store std :: map, so a map will be built for every function call.

Which method would you recommend?

Is it better to encapsulate this compile-time constant array into a class that handles the lookup?

+2


source to share


7 replies


If the values enum

are contiguous starting from 0

, use the enumerated array as the index.

If not, this is what I usually do:

const char* find_language(Language lang)
{
  typedef std::map<Language,const char*> lang_map_type;
  typedef lang_map_type::value_type lang_map_entry_type;

  static const lang_map_entry_type lang_map_entries[] = { /*...*/  }
  static const lang_map_type lang_map( lang_map_entries
                                     , lang_map_entries + sizeof(lang_map_entries)
                                                        / sizeof(lang_map_entries[0]) );
  lang_map_type::const_iterator it = lang_map.find(lang);
  if( it == lang_map.end() ) return NULL;
  return it->second;
}

      



If you are considering a map for constants, always consider using a vector as well.

Function-local static is a great way to get rid of a lot of globals dependency problems, but they are dangerous in a multithreaded environment. If you're worried about this, chances are you want to use globals:

typedef std::map<Language,const char*> lang_map_type;
typedef lang_map_type::value_type lang_map_entry_type;

const lang_map_entry_type lang_map_entries[] = { /*...*/  }
const lang_map_type lang_map( lang_map_entries
                            , lang_map_entries + sizeof(lang_map_entries)
                                               / sizeof(lang_map_entries[0]) );

const char* find_language(Language lang)
{
  lang_map_type::const_iterator it = lang_map.find(lang);
  if( it == lang_map.end() ) return NULL;
  return it->second;
}

      

+3


source


There are three main approaches that I would take. One is a switch statement, and under certain conditions this is a very good option. Remember - the compiler is probably going to compile this into an efficient table lookup for you, although it will look for pointers to case code blocks, not data values.

Parameters two and three include static arrays of the type you are using. The second option - a simple linear search, which you already (I think) already does - is very suitable if the number of items is small.

Option three is binary search. Static arrays can be used with the standard library algorithms - just use first and first + pointers in the same way as start and end iterators. You will need to ensure the data is sorted (using std :: sort or std :: stable_sort) and use std :: lower_bound to do binary search.

The complication in this case is that you need a comparison function object that acts like the <operator with a stored or referenced value, but that only looks at the key field of your structure. Below is a sample template ...

class cMyComparison
{
  private:
    const fieldtype& m_Value;  //  Note - only storing a reference

  public:
    cMyComparison (const fieldtype& p_Value) : m_Value (p_Value) {}

    bool operator() (const structtype& p_Struct) const
    {
      return (p_Struct.field < m_Value);
        //  Warning : I have a habit of getting this comparison backwards,
        //            and I haven't double-checked this
    }
};

      

This kind of thing should be easier in the next version of C ++, when IIRC we get anonymous functions (lambdas) and closures.



If you can't put sorting in your application initialization, you may need an already sorted boolean static so that you only sort once.

Note. This is just for information - in your case, I think you should either stick with linear search or use a switch statement. Binary search is probably a good idea when ...

  • There are many items to search
  • Searches are performed very often (many times per second).
  • Key enumeration values ​​are sparse (many large gaps) - otherwise the switch is better.

If the coding effort was trivial, it wouldn't matter, but C ++ currently makes it a little more difficult than it should be.

One minor note - it might be a good idea to define an enum for the size of your array and ensure that the static array declaration uses that list. So your compiler should complain if you change the table (add / remove elements) and forget to update the size enumeration, so your searches should never skip elements or go out of bounds.

+2


source


I think you have two questions:

  • What is the best way to store a constant global variable (with possible multi-threaded access)?
  • How to store your data (which container to use)?

The solution described by sbi is elegant, but you should be aware of two potential problems:

  • In the case of multi-threaded access, the initialization can be hidden.
  • You will try to access this variable after destroying it.

Both problems related to the lifetime of static objects are covered in another thread .

Start with the problem of storing a constant global variable .

So the solution suggested by sbi is adequate if you are not worried about 1. or 2. otherwise I would recommend using Singleton, like the ones provided by Loki . Read the relevant documentation to understand the different policies throughout life, this is very valuable.

I think using array + map seems wasteful and it hurts me to read this. I personally prefer a slightly more elegant solution (imho).

const char* find_language(Language lang)
{
  typedef std::map<Language, const char*> map_type;
  typedef lang_map_type::value_type value_type;

  // I'll let you work out how 'my_stl_builder' works,
  // it makes for an interesting exercise and it easy enough
  // Note that even if this is slightly slower (?), it is only executed ONCE!
  static const map_type = my_stl_builder<map_type>()
                          << value_type(GERMAN, "German")
                          << value_type(ENGLISH, "English")
                          << value_type(DUTCH, "Dutch")
                          ....
                          ;

  map_type::const_iterator it = lang_map.find(lang);
  if( it == lang_map.end() ) return NULL;
  return it->second;
}

      

And now let's apply to the container type problem .

If you are concerned about performance, then you should know that for small data collection, a vector of pairs is usually more search efficient than a map. Once again I will refer to Loki (and his AssocVector), but I really don't think you should be concerned about performance.

I prefer to choose my container depending on the interface that I might need in the first place, and here the map interface is really what you want.

Also: why are you using 'const char *' and not 'std :: string'?

I've seen too many people use "const char *" as std :: string (forgetting that you need to use strcmp for example) to be concerned about the alleged memory / performance loss ...

+2


source


It depends on the purpose of the array. If you plan on displaying values ​​in a list (for user selection, perhaps) an array will be the most efficient way to store them. If you plan to search for key values ​​frequently enum

, you should look into a more efficient data structure such as a map.

0


source


There is no need to write a loop. You can use the enumeration value as an index to the array.

0


source


I would do an enum with sequential language codes

enum { GERMAN=0, ENGLISH, SWAHILI, ENOUGH };

      

Put them all in an array

const char *langnames[] = {
    "German", "English", "Swahili"
};

      

Then I would check if sizeof(langnames)==sizeof(*langnames)*ENOUGH

in a debug build.

And pray that I have no duplicates or exchangeable languages; -)

0


source


If you want a quick and easy solution, you can try how to do it

enum ELanguage {GERMAN=0, ENGLISH=1};

    static const string Ger="GERMAN";
    static const string Eng="ENGLISH";

    bool getLanguage(const ELanguage& aIndex,string & arName)
    {
        switch(aIndex)
        {
        case GERMAN:
            {
                arName=Ger;
                return true;
            }
        case ENGLISH:
            {
                arName=Eng;
            }
        default:
            {
                // Log Error
                return false;
            }
        }
    }

      

0


source







All Articles