轻量级包装 - 这是一个常见问题,如果是,它的名字是什么?

时间:2011-07-11 10:10:22

标签: c++ design-patterns locking wrapper idioms

我必须使用一个库来进行非线程安全的数据库调用。另外,我偶尔也需要在后台线程中加载大量数据 很难说哪些库函数实际访问了DB,所以我认为对我来说最安全的方法就是用锁来保护每个库调用。

假设我有一个库对象:

dbLib::SomeObject someObject;

现在我可以这样做:

dbLib::ErrorCode errorCode = 0;
std::list<dbLib::Item> items;
{
    DbLock dbLock;
    errorCode = someObject.someFunction(&items);
} // dbLock goes out of scope

我想将其简化为类似的东西(甚至更简单):

dbLib::ErrorCode errorCode =
    protectedCall(someObject, &dbLib::SomeObject::someFunction(&items));

这样做的主要优点是我不必复制dbLib::SomeObject的接口,以便用锁来保护每个电话。

我很确定这是一种常见的模式/习语,但我不知道它的名字或要搜索的关键字。 (看http://www.vincehuston.org/dp/gof_intents.html我认为,这更像是一种成语而不是一种模式。)

我在哪里可以查找更多信息?

6 个答案:

答案 0 :(得分:8)

你可以使protectedCall成为一个模板函数,它接受一个没有参数的函子(意味着你将绑定调用站点的参数),然后创建一个作用域锁,调用函子,并返回它的值。例如:

template <typename Ret>
Ret protectedCall(boost::function<Ret ()> func)
{
    DbLock lock;
    return func();
}

然后你会这样称呼它:

dbLib::ErrorCode errorCode = protectedCall(boost::bind(&dbLib::SomeObject::someFunction, &items));

编辑。如果您使用的是C ++ 0x,则可以使用std::functionstd::bind代替提升等值。

答案 1 :(得分:5)

在C ++ 0x中,您可以实现某种形式的装饰器:

template <typename F>
auto protect(F&& f) -> decltype(f())
{
    DbLock lock;
    return f();
}

用法:

dbLib::ErrorCode errorCode = protect([&]() 
{
    return someObject.someFunction(&items); 
});

答案 2 :(得分:4)

您的描述这似乎是Decorator Pattern的工作。

但是,特别是在资源方面,我不建议使用它。

原因在于,一般来说,这些函数往往会严重缩放,需要更高级别(更少细粒度)的锁定以保持一致性,或者返回对内部结构的引用,这些内部结构要求锁定保持锁定直到读取所有信息。

想想,例如关于调用返回BLOB(流)或引用游标的存储过程的DB函数:不应该在锁之外读取流。

怎么做?

我建议改为使用Facade Pattern。而不是直接在数据库调用方面编写操作,而是实现使用数据库层的外观;然后,该层可以完全按照所需级别管理锁定(并在需要的地方进行优化:您可以将外观实现为线程本地Singleton,并使用单独的资源,从而避免需要锁定,例如。)

答案 3 :(得分:1)

最简单(也很简单)的解决方案可能是编写一个返回对象代理的函数。代理执行锁定和重载 - &gt;允许调用该对象。这是一个例子:

#include <cstdio>

template<class T>
 class call_proxy 
 {
  T &item; 
  public:

  call_proxy(T &t) : item(t) { puts("LOCK"); }
  T *operator -> () { return &item; }
  ~call_proxy() { puts("UNLOCK"); }
 };

template<class T>
call_proxy<T> protect(T &t) 
{    
 return call_proxy<T>(t);
}

以下是如何使用它:

class Intf
{
 public:
  void function() 
 {
  puts("foo");
 }
};

int main()
{
 Intf a; 
 protect(a)->function(); 
}

输出应为:

LOCK
foo
UNLOCK

如果你想在评估参数之前发生锁定,那么可以使用这个宏:

#define PCALL(X,APPL) (protect(X), (X).APPL)
PCALL(x,x.function());

这会评估x两次。

答案 4 :(得分:1)

Andrei Alexandrescu的

This article有一篇非常有趣的文章,介绍如何创建这种瘦包装,并将其与可怕的volatile关键字相结合,以保证线程安全。

答案 5 :(得分:0)

互斥锁定是一个类似的问题。它在这里寻求帮助:Need some feedback on how to make a class "thread-safe"

我想出的解决方案是一个阻止访问受保护对象的包装类。可以通过“访问者”类获得访问权限。访问器将锁定构造函数中的互斥锁,并在销毁时将其解锁。有关详细信息,请参阅Threading.h中的“ThreadSafe”和“Locker”类。