如何避免在std::unordered_map上进行双重搜索,并在实现缓存时避免在不需要时调用工厂函数

How to avoid double search on std::unordered_map AND avoid calling factory function when not required when implementing a cache

本文关键字:缓存 实现 不需要 函数 工厂 调用 unordered std 何避免 map 搜索      更新时间:2024-09-28

我一直在实现一个基于std::unordered_map的缓存。如果值已经存储,我希望避免调用生成值的工厂函数,但我也希望避免在映射上运行两次搜索。

#include <unordered_map>
struct Value {
int x;
Value & operator=(Value const &) = delete;
};
using Cache = std::unordered_map<int, Value>;
Value make_value(int i){
// Imagine that this takes a time ok!
i = i + 1;
return Value{i};
}
// This has double search
template <typename F>
Value & insert_a(Cache & cache, int key, F factory)
{
auto i = cache.find(key);
if(i==cache.end()){
auto r = cache.try_emplace(key,factory(key));
return r.first->second;
}
return i->second;
}
// This runs the factory even if it is not required
template <typename F>
Value & insert_b(Cache & cache, int key, F factory)
{
auto r = cache.try_emplace(key,factory(key));
return r.first->second;
}
int main(){
std::unordered_map<int,Value> map;
insert_a(map,10,make_value);
insert_b(map,10,make_value);
return 0;
}

我有两个insert的简化实现,演示了如何构建缓存。

insert_a使用find-first来检测项目是否存在,并且仅当它没有调用工厂来获取值时。对容器执行两次搜索。

insert_b调用try_emplace并仅返回存储的值。这显然很糟糕,因为即使值已经存在,也会调用工厂。

我似乎想要一个中间地带,在那里我直接传递工厂函数来尝试_模板,并且只有在需要时才在内部调用它。有没有办法模拟这种情况?

这不是关于如何构建缓存的一般问题。我知道多线程问题、常量正确性和可变关键字。我特别询问如何获得两个

  • 容器的单次搜索
  • 仅在需要时致电工厂

请注意,我故意删除了Value类的复制赋值运算符。一个显而易见的答案是先插入一个默认值,然后覆盖它。并不是所有的类都是可复制赋值的,我想支持这些。

有一个沙盒可以玩https://godbolt.org/z/Gja3MaGWf

您可以使用惰性工厂。只在需要时打电话给实际工厂:

#include <unordered_map>
#include <iostream>
struct Value {
int x;
Value & operator=(Value const &) = delete;
};
using Cache = std::unordered_map<int, Value>;
Value make_value(int i){
// Imagine that this takes a time ok!
i = i + 1;
return Value{i};
}
template <typename F>
Value & insert_b(Cache & cache, int key)
{
auto r = cache.try_emplace(key,F{key});
return r.first->second;
}
// call the factory when needed    
struct ValueMaker {
int value;
operator Value() {
std::cout << "calledn";
return make_value(value);
}
};
int main(){
std::unordered_map<int,Value> map;
insert_b<ValueMaker>(map,10);
insert_b<ValueMaker>(map,10);
return 0;
}

输出为

called

因为当元素被插入到映射中时CCD_ 1仅被调用一次。在第二个调用中,值生成器(只是一个瘦包装器(不会转换为Value,因为key已经在映射中了。

我试着尽可能少地更改你的代码。对于实际的代码,您可能希望去掉两个工厂(make_valueValueMaker(,只使用一个。关键是向try_emplace传递一些轻包装,该包装仅在实际值转换为Value时触发实际值的构造。