使用gcc 4.8.1和libboost 1.53我会得到不同的结果,具体取决于我用来编译代码的优化级别。
作为较大程序的一部分,函数insertValues
对同一a
,key
和value
执行两次:
/* Complex classes */
class A { /*....*/ }
class Value { /*.....*/ }
class SortedVector : public std::vector<T> { /*.....*/ }
/* Hash for the key */
struct KeyHash : std::unary_function<Key, size_t> {
size_t operator()(Key const& x) const {
size_t hash = x.get<0>();
SortedVector<int>* xNumbers = x.get<2>();
if(xNumbers != NULL) {
BOOST_FOREACH(int num, *xNumbers) {
MurmurHash3_x86_32(&num, sizeof(size_t), hash, &hash);
}
}
return hash;
}
};
/* Equals for the key */
struct KeyEqual : std::binary_function<Key, Key, bool> {
size_t operator()(Key const& x, Key const& y) const {
if(x.get<0>() != y.get<0>() || fabs(x.get<1>() - y.get<1>()) > 0.00001 || x.get<3>() != y.get<3>()) {
return false;
}
SortedVector<int>* xNumbers = x.get<2>();
SortedVector<int>* yNumbers = y.get<2>();
if(xNumbers == yNumbers) {
return true;
}
if(xNumbers == NULL || yNumbers == NULL) {
return false;
}
if(xNumbers->size() != yNumbers->size()) {
return false;
}
return std::equal(xNumbers->begin(), xNumbers->end(), yNumbers->begin());
}
};
/* typedefs */
typedef boost::tuple<int, double, SortedVector<int>*, int> Key;
typedef boost::unordered_map<A, boost::unordered_map<Key, Value*, KeyHash, KeyEqual>, A::Hash, A::Equal> Map;
/* code that shows the problem */
void insertValues(A a, Key key, Value* value) {
Map::iterator iter = map->find(a);
if (iter == map->end()) {
iter = map.insert(std::make_pair(a, Map::value_type::second_type())).first;
}
Map::value_type::second_type::iterator keyIter = iter->second.find(key);
if (keyIter == iter->second.end()) {
iter->second.insert(std::make_pair(key, value));
}
}
与-O2
一起编译keyIter
始终等于iter->second.end()
,表示key, value
对不在地图中。但是,第二次运行时,insert
不会插入该对。
在咨询insert
find
和find
之后,我得出结论,虽然insert
找不到对,-O1
找到它并拒绝插入。
使用find
编译代码按预期工作。
有没有人有洞察力,为什么-O1
可能会产生与-O2
和find
不同的结果?或者为什么insert
和{{1}}的查找结果会有所不同?
哈希使用来自boost documentation的MurmurHash3_x86_32。 该系统是OpenSuse x86_64 GNU / Linux。
答案 0 :(得分:5)
最可能的错误来源是你对哈希函数的调用:
BOOST_FOREACH(int num, *xNumbers) {
MurmurHash3_x86_32(&num, sizeof(size_t), hash, &hash);
}
您使用的是OpenSuse x86_64 GNU / Linux,这是一个LP64平台,因此int
为32位,而size_t
(和long
)为64位宽。在the implementation of MurmurHash3_x86_32
中,key
以字节方式访问,也由32位块(uint32_t
)访问。这是正常的,因为it's allowed to alias signed and unsigned variants of the same integral type(以及任何平滑的可复制类型字节),uint32_t
必须是unsigned int
,因为x86_64 Linux上没有其他基本的无符号32位整数类型。
但是,此代码中存在两个错误。首先,len
参数应该是key
的大小,但sizeof(size_t)
在您的平台上为8,而int num
的大小为4个字节。这意味着哈希函数将读取未定义的内存位置(&num + 1
),并且优化编译器可以自由地为此读取提供任何值或导致其他未定义的行为。
其次,您提供&hash
作为out
参数。但是MurmurHash3_x86_32
将*out
写为uint32_t
,而size_t
不能将uint32_t
写为别名,因为这两种类型是不同的算术类型,而不是有符号/无符号变体。这意味着对hash
的写入具有未定义的行为,并且优化编译器可以自由地忽略此写入或导致其他未定义的行为。
这样的事情会更正确:
std::uint32_t result;
MurmurHash3_x86_32(xNumbers->data(),
sizeof(*xNumbers->data()) * xNumbers->size(),
hash, &result);
hash ^= (static_cast<std::uint32_t>(hash) ^ result);
请注意,与您的代码相反,这会在对哈希函数的单个调用中提供所有xNumbers
。这保证在vector
连续存储其元素时起作用,并且更接近于如何使用散列函数;它的设计不是为了重复调用。
答案 1 :(得分:0)
您的条件fabs(x.get<1>() - y.get<1>()) > 0.00001
可以使不同的对象看起来与容器相同。你应该说x.get<1>() != y.get<1>()
。