正如其他答案所说,您正在按值传递备忘录,这样它就不会保留值并重用值。因为按值传递可以防止备忘录被修改,因为只有副本被修改然后丢失。
您必须通过引用传递它。但是通过引用传递有点棘手,因为您必须在函数之外创建备忘录并手动将其传递给函数。其他答案给出了如何通过引用从外部传递它的示例。
我想向您展示其他方法。
第一个变体是创建并传递std::shared_ptr。此方法允许您的函数的用户不必关心自己创建备忘录,他们不需要传递任何东西。不仅如此,这个备忘录函数参数现在可能成为一个可选参数,并且可以通过函数调用自动创建。让我们看看它的样子:
Try it online!
#include <map>
#include <memory>
#include <iostream>
int fib(int n, std::shared_ptr<std::map<int, int>> memo =
std::make_shared<std::map<int, int>>()) {
if (memo->count(n))
return memo->at(n);
if (n <= 2)
return 1;
else {
int result = fib(n - 1, memo) + fib(n - 2, memo);
memo->insert(std::pair<int, int>(n, result));
return result;
}
}
int main() {
std::cout << fib(45) << std::endl;
}
输出:
1134903170
这是 45 斐波那契的正确输出,正如我们在 Wolfram Alpha page 中看到的那样。
可以看到,在上面的方法中,我不必向斐波那契函数传递任何东西,带有共享指针的可选参数是自动创建的。
人们可能很想将可选参数作为可变引用传递,如下所示:
int fib(int n, std::map<int, int> & memo = std::map<int, int>()) {
..........
fib(n - 1, memo) + ...
}
上面的这种语法会非常方便并且可以解决问题,但不幸的是 C++ 不提供将临时对象分配给可变引用的能力。所以上面的代码不起作用,也不能编译。
只能将临时对象分配给const &,如下所示:
std::map<int, int> const & memo = std::map<int, int>()
但 const 引用不允许我们修改底层备忘录并更改其值。
所以在这种情况下,上面使用的共享指针通过允许创建临时对象并允许对其进行修改并递归传递来为我们节省了时间。
另一种解决任务的有趣方法是在函数体内使用thread_local 变量:
Try it online!
#include <map>
#include <memory>
#include <iostream>
int fib(int n) {
thread_local std::map<int, int> memo;
if (memo.count(n))
return memo.at(n);
if (n <= 2)
return 1;
else {
int result = fib(n - 1) + fib(n - 2);
memo.insert(std::pair<int, int>(n, result));
return result;
}
}
int main() {
std::cout << fib(45) << std::endl;
}
上面的解决方案允许我们根本不向函数传递任何东西,而是将 memo 作为本地函数的变量。
线程局部修饰符允许我们在每个线程的备忘录中重用相同的计算值。如果您打算使用来自多个内核和/或线程的函数,这个 thread_local 修饰符很方便。此修饰符允许不使用任何互斥锁来同步对其的写入。但是备忘录值只能在每个线程中单独共享。下一个解决方案通过使用 static 修饰符在所有线程之间共享值来帮助我们。
Try it online!
#include <map>
#include <memory>
#include <iostream>
int fib(int n) {
static std::map<int, int> memo;
if (memo.count(n))
return memo.at(n);
if (n <= 2)
return 1;
else {
int result = fib(n - 1) + fib(n - 2);
memo.insert(std::pair<int, int>(n, result));
return result;
}
}
int main() {
std::cout << fib(45) << std::endl;
}
static 上述方法与线程本地方法相同,允许不向函数传递任何内容。它还允许在不同的调用之间以及不同的线程/内核之间共享所有的备忘录值。但是与线程局部解决方案不同,静态方法不是线程安全的,这意味着没有互斥同步就不能使用它,因此使用静态变量的正确方法如下:
Try it online!
#include <map>
#include <memory>
#include <iostream>
#include <mutex>
int fib(int n) {
static std::recursive_mutex mux;
static std::map<int, int> memo;
std::lock_guard<std::recursive_mutex> lock(mux);
if (memo.count(n))
return memo.at(n);
if (n <= 2)
return 1;
else {
int result = fib(n - 1) + fib(n - 2);
memo.insert(std::pair<int, int>(n, result));
return result;
}
}
int main() {
std::cout << fib(45) << std::endl;
}
上面可以看到我使用了递归互斥锁,这是同步递归函数的正确方法。现在有了这个互斥体,我们的函数是线程安全的,这意味着我们可以在多核线程上使用它。