Herb Sutter 的 10 衬里清洁
Herb Sutter's 10-liner with cleanup
我一直在为我的代码添加数据缓存并记得 "Herb Sutter's favorite 10-liner.":
shared_ptr<widget> get_widget(int id) {
static map<int, weak_ptr<widget>> cache;
static mutex m;
lock_guard<mutex> hold(m);
auto sp = cache[id].lock();
if (!sp) cache[id] = sp = load_widget(id);
return sp;
}
我看到的一个批评是,根据上下文,从缓存中删除任何内容可能是一个问题。
我很惊讶在任何地方都找不到自动清理解决方案。你怎么看这个?
std::shared_ptr<widget> get_widget(int id) {
static std::map<int, std::weak_ptr<widget>> cache;
static std::mutex m;
std::lock_guard<std::mutex> hold(m);
auto& weakCached = cache[id]; // Keep a reference so we don't hae to call this non-nothrow function below.
auto sp = weakCached.lock();
if (!sp) {
std::unique_ptr<widget> it = load_widget(id);
// We have to be careful about the deleter not being
// called while in the mutex lock 'cause that'd be a
// double lock => deadlock.
// For that reason, we already have a
auto deleter = [&,id,d=it.get_deleter()](widget* w) {
std::lock_guard<std::mutex> hold(m);
d(w);
cache.erase(id);
};
sp = std::shared_ptr<widget>(it.get(), deleter);
it.release(); // In the case that the above line throws, we won't hit this line and so won't leak.
weakCached = sp;
}
return sp;
}
正如评论所说,让我停顿的部分是将所有权转移到 deleter
函数。特别是,调用它会锁定互斥体,因此在互斥体被锁定时无法调用它。我认为这正确地保证了它不会在关键部分内被调用,特别是因为如果 sp = std::shared_ptr<widget>(it.get(), deleter);
抛出,那么它仍然属于 unique_ptr
并且下面的行让它去。
奖励积分:如果 load_widget(int id)
returns 一个 std::shared_ptr<widget>
可能已经有一个非平凡的删除器怎么办。有没有办法在它周围注入这个包装器,或者这是不可能的?
我相信有一种更简单的方法可以保证删除器不会 运行 而 hold
持有 锁:声明 sp
在 hold
之前,所以当我们超出范围时,互斥量将在删除器可以 运行:
之前释放
std::shared_ptr<widget> sp;
std::lock_guard<std::mutex> hold(m);
auto& weakCached = cache[id];
sp = weakCached.lock();
重用 weakCached
是有意义的,即使不必关心异常安全。
What if load_widget(int id)
returns a std::shared_ptr<widget>
这是一个(疯狂的?)想法:
std::shared_ptr<widget> it = load_widget(id);
widget* ptr = it.get();
auto deleter = [&,id,original=std::move(it)](widget*) {
std::lock_guard<std::mutex> hold(m);
cache.erase(id);
};
weakCached = sp = std::shared_ptr<widget>(ptr, std::move(deleter));
我们在删除器中保留了 load_widget
结果的副本。这会导致我们的共享指针保持原始共享指针处于活动状态。当我们的引用计数用完时,我们的自定义删除器不会直接对小部件执行任何操作。删除器被简单地销毁,因此捕获的共享指针被销毁,如果它是唯一的所有者,那么原始的自定义删除器将完成它的工作。
这种方法有一个警告:如果原始共享指针的副本以某种方式在其他地方可用,那么它的生命周期可能会延长到我们共享指针的生命周期之外,因此缓存可能会被过早清除。是否是这种情况,这是否是一个问题取决于有问题的小部件 API。
这种方法也可以用于您独特的指针情况。
我一直在为我的代码添加数据缓存并记得 "Herb Sutter's favorite 10-liner.":
shared_ptr<widget> get_widget(int id) {
static map<int, weak_ptr<widget>> cache;
static mutex m;
lock_guard<mutex> hold(m);
auto sp = cache[id].lock();
if (!sp) cache[id] = sp = load_widget(id);
return sp;
}
我看到的一个批评是,根据上下文,从缓存中删除任何内容可能是一个问题。
我很惊讶在任何地方都找不到自动清理解决方案。你怎么看这个?
std::shared_ptr<widget> get_widget(int id) {
static std::map<int, std::weak_ptr<widget>> cache;
static std::mutex m;
std::lock_guard<std::mutex> hold(m);
auto& weakCached = cache[id]; // Keep a reference so we don't hae to call this non-nothrow function below.
auto sp = weakCached.lock();
if (!sp) {
std::unique_ptr<widget> it = load_widget(id);
// We have to be careful about the deleter not being
// called while in the mutex lock 'cause that'd be a
// double lock => deadlock.
// For that reason, we already have a
auto deleter = [&,id,d=it.get_deleter()](widget* w) {
std::lock_guard<std::mutex> hold(m);
d(w);
cache.erase(id);
};
sp = std::shared_ptr<widget>(it.get(), deleter);
it.release(); // In the case that the above line throws, we won't hit this line and so won't leak.
weakCached = sp;
}
return sp;
}
正如评论所说,让我停顿的部分是将所有权转移到 deleter
函数。特别是,调用它会锁定互斥体,因此在互斥体被锁定时无法调用它。我认为这正确地保证了它不会在关键部分内被调用,特别是因为如果 sp = std::shared_ptr<widget>(it.get(), deleter);
抛出,那么它仍然属于 unique_ptr
并且下面的行让它去。
奖励积分:如果 load_widget(int id)
returns 一个 std::shared_ptr<widget>
可能已经有一个非平凡的删除器怎么办。有没有办法在它周围注入这个包装器,或者这是不可能的?
我相信有一种更简单的方法可以保证删除器不会 运行 而 hold
持有 锁:声明 sp
在 hold
之前,所以当我们超出范围时,互斥量将在删除器可以 运行:
std::shared_ptr<widget> sp;
std::lock_guard<std::mutex> hold(m);
auto& weakCached = cache[id];
sp = weakCached.lock();
重用 weakCached
是有意义的,即使不必关心异常安全。
What if
load_widget(int id)
returns astd::shared_ptr<widget>
这是一个(疯狂的?)想法:
std::shared_ptr<widget> it = load_widget(id);
widget* ptr = it.get();
auto deleter = [&,id,original=std::move(it)](widget*) {
std::lock_guard<std::mutex> hold(m);
cache.erase(id);
};
weakCached = sp = std::shared_ptr<widget>(ptr, std::move(deleter));
我们在删除器中保留了 load_widget
结果的副本。这会导致我们的共享指针保持原始共享指针处于活动状态。当我们的引用计数用完时,我们的自定义删除器不会直接对小部件执行任何操作。删除器被简单地销毁,因此捕获的共享指针被销毁,如果它是唯一的所有者,那么原始的自定义删除器将完成它的工作。
这种方法有一个警告:如果原始共享指针的副本以某种方式在其他地方可用,那么它的生命周期可能会延长到我们共享指针的生命周期之外,因此缓存可能会被过早清除。是否是这种情况,这是否是一个问题取决于有问题的小部件 API。
这种方法也可以用于您独特的指针情况。