Strange unordered map situation

2019-07-04 01:53发布

问题:

I have a shared library with class:

// HPP:
class WorldSettings
{
    private:
        static std::unordered_map<std::string, int> mIntegerStorage;
        static std::unordered_map<std::string, float> mFloatStorage;
        static std::unordered_map<std::string, std::string> mStringStorage;

    public:
        template <typename T>
        static T &Get(const std::string &key);

        template <typename T>
        static T &Set(const std::string &key, T value);
};

// CPP:

#define DoReturn(MapName, Key, Type) {                                                \
    assert( MapName.find(Key) != MapName.end() && "No such key in world settings!" ); \
    return MapName[Key];                                                              \
}                                                                                     \

#define DoSetup(MapName, Key, Value) { \
    MapName[key] = Value;              \
    return MapName[Key];               \
}                                      \

std::unordered_map<std::string, int> WorldSettings::mIntegerStorage;
std::unordered_map<std::string, float> WorldSettings::mFloatStorage;
std::unordered_map<std::string, std::string> WorldSettings::mStringStorage;

// Getters

template <>
int &WorldSettings::Get<int>(const std::string &key)
{
    DoReturn(mIntegerStorage, key, int);
}

template <>
float &WorldSettings::Get<float>(const std::string &key)
{
    DoReturn(mFloatStorage, key, float);
}

template <>
std::string &WorldSettings::Get<std::string>(const std::string &key)
{
    DoReturn(mStringStorage, key, std::string);
}

// Setters

template <>
int &WorldSettings::Set<int>(const std::string &key, int value)
{
    DoSetup(mIntegerStorage, key, value);
}

template <>
float &WorldSettings::Set<float>(const std::string &key, float value)
{
    DoSetup(mFloatStorage, key, value);
}

template <>
std::string &WorldSettings::Set<std::string>(const std::string &key, std::string value)
{
    DoSetup(mStringStorage, key, value);
}

Now I want to use this class in non shared library (simple console application):

WorldSettings::Get<int>("WorldMinutes");

The 'WorldMinutes' is set in shared library code:

WorldSettings::Set<int>("WorldMinutes", 0);

The problem is:

Floating point exception

Program received signal SIGFPE, Arithmetic exception.
0x00000000004d1f61 in std::__detail::_Mod_range_hashing::operator() (this=0x747863, __num=732984944481197501, 
    __den=0) at /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/../../../../include/c++/4.6.2/bits/hashtable_policy.h:376
376     { return __num % __den; }

Backtrace:

#0  0x00000000004d1f61 in std::__detail::_Mod_range_hashing::operator() (this=0x747863, __num=732984944481197501, 
    __den=0) at /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/../../../../include/c++/4.6.2/bits/hashtable_policy.h:376
#1  0x00000000004d3503 in std::__detail::_Hash_code_base<std::string, std::pair<std::string const, int>, std::_Select1st<std::pair<std::string const, int> >, std::equal_to<std::string>, std::hash<std::string>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index (this=0x747860, __c=732984944481197501, __n=0)
    at /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/../../../../include/c++/4.6.2/bits/hashtable_policy.h:758
#2  0x00000000004d2a9a in std::__detail::_Map_base<std::string, std::pair<std::string const, int>, std::_Select1st<std::pair<std::string const, int> >, true, std::_Hashtable<std::string, std::pair<std::string const, int>, std::allocator<std::pair<std::string const, int> >, std::_Select1st<std::pair<std::string const, int> >, std::equal_to<std::string>, std::hash<std::string>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, false, false, true> >::operator[] (this=0x747860, __k=...)
    at /usr/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/../../../../include/c++/4.6.2/bits/hashtable_policy.h:543
#3  0x00000000004d1cfa in WorldSettings::Set<int> (key=..., value=0)

The error is called when Set<int>(...) in shared library code. The interesting thing is that I don't have an error when call Get from library code. What could be wrong here?

回答1:

The code is incorrect as it is.

The specializations you define in the .cpp file should have been declared (out of the class) in the .hpp file, otherwise any time you try to use the defined function you have undefined behavior.

Add:

template <>
int &WorldSettings::Get<int>(const std::string &key);

template <>
float &WorldSettings::Get<float>(const std::string &key);

template <>
std::string &WorldSettings::Get<std::string>(const std::string &key);

// Setters

template <>
int &WorldSettings::Set<int>(const std::string &key, int value);

template <>
float &WorldSettings::Set<float>(const std::string &key, float value);

template <>
std::string &WorldSettings::Set<std::string>(const std::string &key, std::string value);

right after the class definition.

This tells the compiler that the definitions are generated elsewhere.

As it is, it is likely that the shared library will generate its own copy of the functions (did you provided some default template implementation ?) and that may cause havoc.

It may not be the cause of the bug, but it does not make sense to investigate further.



回答2:

This isn't an answer, but a strongly recommended improvement suggestion: Get rid of those macros, and use C++ templates:

template <typename Map>
typename Map::mapped_type const & cget(Map const & m, typename Map::key_type const & k)
{
  typename Map::const_iterator it = m.find(k);
  assert(it != m.cend());
  return it->second;
}

template <typename Map>
typename Map::mapped_type & get(Map & m, typename Map::key_type const & k)
{
  typename Map::iterator it = m.find(k);
  assert(it != m.end());
  return it->second;
}

template <typename Map>
typename Map::mapped_type & put(Map & m, typename Map::key_type const & k, typename Map::mapped_type const & v)
{
  std::pair<typename Map::iterator, bool> p = m.insert(typename Map::value_type(k, v));
  return p.first->second;
}

Now you can say:

template <>
int & WorldSettings::Get<int>(const std::string & key)
{
  get(mIntegerStorage, key);
}

template <>
float & WorldSettings::Set<float>(const std::string & key, float value)
{
  put(mFloatStorage, key, value);
}

You could even move the specializations to the container: The getter/setter becomes, generically:

template <typename T> static T & Get(const std::string & key)
{
  return get(Storage<T>::container, key);
}
template <typename T> static T & Set(const std::string & key, T value)
{
  return put(Storage<T>::container, key, value);
}

You just need a nested class:

template <typename T> struct Storage
{
  static std::unordered_map<std::string, T> container;
};

Now all you need is to provide concrete object instances for the containers that you wish to use:

template <>
std::unordered_map<std::string, int> WorldSettings::Storage<int>::container{};