C++ C++;函数输出的简单缓存设计

C++ C++;函数输出的简单缓存设计,c++,caching,C++,Caching,我想这是一个非常常见的问题,与众所周知的解决方案,而我不是 能够找到。所以我在这里寻求建议 问题陈述 考虑以下设置: class A; // some class const A f(const A&); // an _expensive_ function void do_stuff() { A a; a.modify(...); do_stuff1(f(a)); // compute f(a) do_stuff2(f(a)); // use

我想这是一个非常常见的问题,与众所周知的解决方案,而我不是 能够找到。所以我在这里寻求建议

问题陈述 考虑以下设置:

class A; // some class

const A f(const A&); // an _expensive_ function

void do_stuff()
{
    A a;

    a.modify(...);

    do_stuff1(f(a));  // compute f(a)
    do_stuff2(f(a));  // use cached value of f(a)

    a.modify(...);

    do_stuff3(f(a));  // recompute f(a)
}
我希望
f(a)
的返回值在第一个和第二个 第二次调用,但在第二次调用
a.modify()
后将被丢弃。 EDIT:实际上,对
f(a)
的调用将在不同的范围内

以下是我已经探索过的解决方案,值得一试

解决方案1:中央缓存 使用时间戳 我可以想象一个简单的解决方案,包括在类
a
中添加一个时间戳 函数
f
可以检查并决定是否需要更新其缓存结果, 存储在中央缓存的某个地方。我想这也意味着改变
f
签名人:

const A& f(const A&);
问题1:对于中央缓存,我们需要一种机制来销毁 销毁
a
f(a)
的缓存结果

使用散列码 除了问题1,这似乎很简单。但是当
A
表示std::vector。我想应该排除动态多态性 在这里所以我们忘了在
std::vector
的子类中添加时间戳 这意味着什么。但是,我们可以计算一些哈希代码或UUID 基于
a
的内容——假设它比计算便宜得多
f(a)
——并将中央缓存基于这些散列码。但我们面临的是 问题1

解决方案2:耦合对象 我还没有找到如何实现这一点,但想法是让
a
notify 当
a
被写入或销毁时
f(a)
的缓存,但当它被 仅仅是阅读。如果没有动态多态性,我想不出怎么做, 使用
操作符[]
或 迭代器,为每个修改的元素向缓存发送通知

问题2:找到一种机制,对
a
的更改集进行定界,以便对每组更改仅使缓存失效一次

我曾想到代理可以在
a
上启用写访问(受此概念启发) 但无法生成任何工作代码


有什么想法吗?

你就不能这样做:

const A &cacheA = f(a);
do_stuff1(cacheA);  // compute f(a)
do_stuff2(cacheA);  // use cached value of f(a)

我在界面上也做过类似的工作,比如:

class F
{
public:
 virtual int f(int a)=0;
};

class Cache : public F
{
public:
   Cache(F &f) : f(f) { }
   int f(int a) { /*caching logic here, calls f.f() if not found from cache */ }
   F &f;
};

class Impl : public F
{
   int f(int a) { /* real implementation here */ }
};
然后决定在何处使用缓存逻辑:

   Impl i; 
   Cache c(i);
   c.f(10); // put to cache with key 10
   c.f(10); // found from cache
   c.f(11); // put to cache with key 11

我可能在这里遗漏了一些重要的细节,但您不能仅为此目的使用a吗?

将f作为a的成员。然后您可以在a的实例中决定是否可以重用缓存的结果。

您不能存储
返回值并重用它吗?如果您可以更改
f(常数A&)
,则可以将
A
的临时对象作为第二个参数传递,并使用相同的参数。@Munger,您提到
A
可以是任何东西,甚至
std::vector
A
是什么?这是理论问题还是实践问题?什么是
A
准确地说?@dialuticus:对于手头的问题,它是一个
std::vector
。但是我记得在其他类型的对象中也遇到了这个问题,所以我写了
A
,也包括了这些情况。也许我不应该。比较两个向量比调用一次函数便宜得多吗?如果这两个操作的成本大致相同,那么就没有太多的优化空间。@Dialogus:是的,比较向量要比调用函数便宜得多。可能不是。所示代码是一个概念性的示例,对
f(a)
的两个调用可能位于代码中完全不相关的部分。不,我上面给出的代码过于简化了。对f(a)的调用实际上发生在相互排斥的作用域中。@Munger:那么你应该编辑你的问题,并更清楚地说明调用将在不同的作用域中。我喜欢这样。但我还是要解决上面的问题1
f
将在许多不同的对象上被调用,因此为所有已销毁的
old_a
保留
f(old_a)
的缓存值是个坏主意。@munger销毁缓存值的方式有两种:缓存对象超出范围/被销毁/替换,或者,您可以向缓存中添加一个成员函数,该函数允许您从缓存中显式删除一些缓存值或一系列值。缓存可以有您喜欢的任何成员函数,因此可以通过这种设计来实现这些功能。我使用boost::function的类似方法,通过调用可能多次调用该函数的缓存版本和其他地方的未缓存版本,成功地实现了所需的功能。在缓存映射中,我将需要大量存储的向量替换为SHA-1散列值。这主要是因为我已经知道缓存对象的最佳生存时间,即直到原始对象被销毁为止。因为在我所想到的特定应用程序中,大多数应用程序的生命周期都很短,所以LRU缓存将是内存的浪费。不过,这一点很好。在我想到的特定应用程序中,
f(a)
只是
a
的一种替代表示形式。所以,出于设计原因,你建议的是我想要走的路,以及我的第一个想法。这就提出了两个问题:(1)如何对没有虚拟成员的std::vector进行子类化,以及(2)何时设置对象的修改标志。