boost中的scoped_lock给互斥/锁操作带来方便, 使用scoped_lock可以避免疏忽导致unlock遗漏. scoped_lock的原理非常简单, 基于栈上的ctor/dtor, 而boost中的实现为了面面俱到, 显得有些罗嗦. 为了突出去模板化的关键之处, 下面所举例子是简化版本.

scoped_lock通常这么用

scoped_lock<mutex_type> lock(mutex);

后面带个模板参数有没有觉得很别扭? 要是scoped_lock不是模板类就好了, 不用跟上讨厌的模板参数. 当然, 可以用typedef达到目的, 但这样就没有通用性了. 本文将去除scoped_lock的类模板, 达到如下效果

scoped_lock lock(mutex);

写一个试试看

struct scoped_lock {
  template<typename Mutex>
  explicit scoped_lock(Mutex & m){
    m.lock();
...
  }
private:
};

很快就发现写不下去了, 我们只能用void *的成员变量保存mutex, 但这样就丢失了类型信息, 导致无法在析构中调用unlock. 对boost::shared_ptr熟悉的朋友可能已经想到shared_ptr了, 它可以保存类型信息, 因为

shared_ptr<void> ptr(new SomeClass);

在expire时,可以正常调用SomeClass的析构函数. 那么就利用shared_ptr来试试

struct scoped_lock {
  template<typename Mutex>
  explicit scoped_lock(Mutex & m) :
    _v(&m, unlock<Mutex>){
    m.lock();
  }
private:
  /// 自定义deleter, 完成unlock操作
  template<typename Mutex>
  static void unlock(Mutex * m){
    m->unlock();
  }
  shared_ptr<void> _v;
};

这个实现利用shared_ptr的自定义deleter来调用unlock, 没有显式的堆操作, 看起来还不错, 而且它的确可以正常工作. 但是, 如果看穿了shared_ptr在这其中的作用, 那么, 完全有理由抛弃它, 至少它的引用计数功能在这里是不需要的. 看一下最后的版本

struct scoped_lock {
  template<typename Mutex>
  explicit scoped_lock(Mutex & m) :
    _m(&m), _u(unlock<Mutex>){
    m->lock();
  }
  ~scoped_lock(){
    _u(_m);///< 实际调用unlock<Mutex>
  }
private:
  void * _m; ///< 保存了mutex指针
  typedef void (*U)(void*);
  U _u; ///< 指向unlock函数的指针
  template<typename Mutex>
  static void unlock(void * m){
    ((Mutex*)m)->unlock(); ///< 强转
  }
};

这个版本用一个对象指针加一个静态函数指针, 取代了shared_ptr, 在时间和空间上都优于前一个版本. 窃以为接近最优了. 当然, 不排除这里面还有没考虑周全的地方. 相对于boost原版, 它多占用了一个静态函数指针的空间.

延伸: 函数模板参数可以自动推导, 但类模板参数不能. 仔细观察上面的例子, 不难看出, 这其实在某种意义上实现了类模板参数的自动推导.