在 C++ unordered_map 中有效地使用 [] 运算符
Posted
技术标签:
【中文标题】在 C++ unordered_map 中有效地使用 [] 运算符【英文标题】:Using The [] Operator Efficiently With C++ unordered_map 【发布时间】:2011-08-01 11:27:36 【问题描述】:首先有人可以澄清一下在 C++ 中使用 [] 运算符和 unordered_map 进行查找是否包含对 find() 方法的调用,或者使用 [] 运算符是否比 find() 更快?
其次,在下面的代码中,我怀疑在键不在 unordered_map 中的情况下,我正在通过 map[key] = value
行进行第二次查找,以便替换使用创建的默认值当键不存在时的 [] 运算符。
这是真的吗,如果是这样的话,有没有办法(可能通过使用指针或其他东西)我可能在任何情况下都只执行一次查找(可能通过存储放置值/读取值的地址from) 并且仍然实现相同的功能?如果是这样,显然这将是一个有用的效率改进。
这里是修改后的代码摘录:
int stored_val = map[key]; // first look up. Does this wrap ->find()??
// return the corresponding value if we find the key in the map - ie != 0
if (stored_val) return stored_val;
// if not in map
map[key] = value;
/* second (unnecessary?) look up here to find position for newly
added key entry */
return value;
【问题讨论】:
【参考方案1】:operator[]
将为您插入一个具有默认构造值的条目(如果尚不存在)。它等效于,但可能会比以下更有效地实现:
iterator iter = map.find(key);
if(iter == map.end())
iter = map.insert(value_type(key, int())).first;
return *iter;
operator[]
可以比使用 find()
和 insert()
手动执行工作更快,因为它可以省去重新散列密钥的麻烦。
您可以解决在代码中进行多次查找的一种方法是引用该值:
int &stored_val = map[key];
// return the corresponding value if we find the key in the map - ie != 0
if (stored_val) return stored_val;
// if not in map
stored_val = value;
return value;
请注意,如果映射中不存在该值,operator[]
将默认构造并插入一个。因此,虽然这将避免多次查找,但如果使用默认构造 + 分配比复制或移动构造更慢的类型,它实际上可能会更慢。
虽然int
默认构造为 0,但您可以将 0 视为一个表示空的幻数。您的示例中可能就是这种情况。
如果你没有这样的幻数,你有两个选择。您应该使用什么取决于您计算该值的成本。
首先,当散列密钥很便宜但计算值很昂贵时,find()
可能是最佳选择。这将散列两次,但仅在需要时计算值:
iterator iter = map.find(key);
// return the corresponding value if we find the key in the map
if(iter != map.end()) return *iter;
// if not in map
map.insert(value_type(key, value));
return value;
但如果你已经获得了价值,你可以非常有效地做到这一点——也许比使用上面的引用 + 幻数更有效:
pair<iterator,bool> iter = map.insert(value_type(key, value));
return *iter.first;
如果map.insert(value_type)
返回的布尔值为真,则该项目已插入。否则,它已经存在并且没有进行任何修改。返回的迭代器指向映射中插入的或现有的值。对于您的简单示例,这可能是最佳选择。
【讨论】:
+1:引用的使用是我平时做的,可读性强,简洁高效 理论上我认为可能存在这样的方法,所以我非常感谢您向我展示如何做到这一点并增加您的专业知识。考虑到我在执行过程中使用了这个功能几十万次,这应该会为我节省大量的执行时间。非常感谢 另外,关于您的编辑:0 在我的程序上下文中是一个有意义的数字,但是当我存储第一个值时,我会记下它的键(使用静态全局变量)并在随后的调用中如果 stored_val 等于 0,我将进行密钥检查,以查看当前密钥是否等于映射到值 0 的密钥。这解决了问题,但感谢您的有效关注。operator[]
也可能比find()
加上insert()
慢,因为它必须默认构造对象,然后分配给它,其中find()
加上insert()
构造(可能C++03 中的一个附加复制结构)。这是否比重新散列更贵或更便宜(或者实现是否必须重新计算它刚刚查找的值的散列)将取决于。你无法真正提前知道。
在地图中我使用的是原始类型,所以这是否避免了您提到的对象的构造,我想在使用引用类型的地图中会发生这种情况?【参考方案2】:
您可以检查一个元素是否存在,和如果它不存在则插入一个新元素,使用特殊的insert
函数返回一个pair<iterator, bool>
,其中布尔值告诉你如果该值已被实际插入。比如代码here:
unordered_map<char, int> mymap;
pair<unordered_map<char,int>::iterator,bool> ret;
// first insert function version (single parameter):;
mymap.insert ( pair<char,int>('z',200) );
ret=mymap.insert (pair<char,int>('z',500) );
if (ret.second==false)
cout << "element 'z' already existed";
cout << " with a value of " << ret.first->second << endl;
此处的代码将<'z',200>
对插入到映射中(如果它不存在)。如果返回的对的第二个元素的值为真,则返回插入的迭代器;如果对的第二个元素为假,则返回元素实际所在的迭代器。
【讨论】:
这是一个有用的答案,非常感谢您的意见。我想我会选择其他一个使用存储引用的方法,因为它在可读性方面看起来更清晰(我不确定效率的差异!),但还是谢谢你们。非常感谢您的帮助。 在计算值很昂贵的(通常)情况下,始终无法插入。在这种情况下,映射用于避免重新计算值 - 这是您的代码完全缺乏的优势。 @Sjoerd,根据问题,计算值似乎不是这个过程的关键耗时任务。如果不是,为什么要尝试优化对(哈希)映射的一两次访问,这主要是 O(1)? @Diego 好点,虽然计算哈希也可能很耗时。 @Diego 请注意,耗时的计算也是 O(1),因为它很可能不依赖于 unordered_map 的大小。所以总是重新计算是 O(1) - 即使重新计算需要很长时间。【参考方案3】:首先有人可以澄清一下在 C++ 中使用 [] 运算符和 unordered_map 进行查找是否包含对 Find() 方法的调用,或者使用 [] 运算符是否比 Find() 更快?
对此没有规定。 []
的实现可以使用find()
,它可以自行执行查找,也可以将查找委托给find()
在内部使用的某个私有方法。
也不能保证哪个更快。 find()
涉及构造和返回迭代器的开销,而 []
如果键不存在可能会更慢,因为在这种情况下它会插入一个新值。
(...) 有没有一种方法(可能通过使用指针或其他方式)我可能在任何情况下都只执行一次查找 (...)
如果键不在映射中,[]
将插入一个新的默认构造值,并返回一个引用。因此,您可以存储该引用以保存第二次查找:
int& stored_val = map[key]; // Note the reference
if (stored_val) return stored_val;
// Use the reference to save a second lookup.
stored_val = value;
return value;
【讨论】:
这看起来正是我所追求的,非常感谢。只是出于兴趣,如果 & 是“地址”,那么为什么说 *stored_val = value; 是不正确的?其中 * 表示“存储的值”?请纠正我对此处语法的可能误解!&
在 C++ 中用于“地址”(获取指针)和引用(隐式指针)。这里,int&
是一个引用,而不是一个指针(这将是int*
)。您不必取消引用引用,因此无需编写*stored_value = ...
。以上是关于在 C++ unordered_map 中有效地使用 [] 运算符的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C++ 中单独锁定 unordered_map 元素
C++ - unordered_map 运算符 [],意外行为