C++多线程环境下的构造函数

多线程的环境里,咱们总不可避免要使用锁。因而一个常见的场景就是:多线程

 1 class ObjectWithLock
 2 {
 3 private:
 4   std::mutex mtx_;
 5   SomeResType shared_res_;
 6 
 7 public:
 8   // Constructor/Destructor
 9 10 
11   void OpOnSharedRes()
12   {
13     std::lock_guard<std::mutex> lock(mtx_);
14 
15     // read/write shared resource: shared_res_
16 17   }
18 };

问题

多线程环境下该如何实现拷贝构造函数和移动构造函数呢?函数

要实现默认构造函数,没有挑战。使用成员初始化列表就能够:性能

ObjectWithLock() : mtx_(), shared_res_() {} // 若是SomeResType有默认构造函数的话。spa

那么咱们的拷贝构造怎么写呢?线程

方案一

很是有可能见到的一种解决方案是:设计

1 ObjectWithLock(ObjectWithLock const & rhs)
2 {
3   std::lock_guard<std::mutex> lock(rhs.mtx_);
4 
5   shared_res_ = rhs.shared_res_;
6 }

优势

很明显,这个方案解决了咱们的问题。代理

缺点

方案一的缺点也是显而易见的。就是shared_res_ = rhs.shared_res_。为何这么说呢?code

咱们先来复习下成员变量初始化顺序。对象

C++成员变量的初始化顺序

成员变量的初始化顺序在C++里弄很差,就是各类坑。blog

首先咱们比较容易知道的是,C++成员变量的初始化发生在构造函数以前。

那么成员变量之间初始化的顺序是如何的呢?

没有专门去注意过这个问题的童鞋,极可能会认为,成员变量的初始化顺序是有成员变量初始化列表的顺序决定的。而事实上成员变量的初始化顺序是有其声明顺序决定的。

方案一的问题

因此方案一的问题就是,shared_res_ = rhs.shared_res_,作的是赋值而不是初始化。若是咱们要过度看待这个问题的话,那么结论就是这会带来性能损耗(看具体状况,对性能存在很大的影响也是有可能的)。

固然不少时候咱们没有必要吹毛求疵。事实上方案一最大的问题是他有不少局限性。这些局限性要求SomeResType必须:

  1. 有无参默认构造
  2. 支持赋值操做
  3. 没有const成员变量,没有引用类型的成员变量等

若是SomeResType不支持这些前提条件,那么为了解决当前的问题,要么让SomeResType支持,要么从新设计ObjectWithLock。若是ObjectWithLock是一个正确的设计想法,那么就只能为难SomeResType了。蛋疼。

难道这个问题就无解了不成?固然不是这样的,不过若是按照C++98/03的标准来作,真的不容易。咱们仍是来看看C++11能给咱们带来什么惊喜吧。

方案二

C++11引入了一个和构造函数相关的语法,delegate constructor,代理构造函数。一个在C#里早就存在的语法,终于C++里也有了。

所谓的delegate constructor,就是构造函数的初始化能够代理(delegates to)给该类另一个构造函数去完成。一个简单的例子(来自C++11标准文档12.6.2,段落6)就是:

1 struct C
2 {
3   C(int c) {}     // #1: non-delegating constructor
4   C() : C(42) {}  // #2: delegate to #1
5 };

有了代理构造函数,咱们的问题就容易解决了。咱们要解决的问题的关键就是,在初始化成员变量前,先将rhs对象lock住。

那么原先的拷贝构造函数要作到和下面伪代码相似的事情:

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  : shared_res_(rhs.shared_res_), lock(rhs)
{}

借助代理构造函数,咱们先实现一个自定义的构造函数,可以作拷贝,而且接受一把锁:

1 ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs,
2                                std::lock_guard<std::mutex> const &)
3   : shared_res_(rhs.shared_res_)
4 {}

而后很天然我就有了:

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  ObjctWithLock(rhs, std::lock_guard<std::mutex>(rhs.mtx_))
{}

问题完美解决了!

C++98/03的解决方案

C++98/03到底能不能解决这个问题呢?能!固然能!就是看你能不能想到了。

这里我就直接给结果,留给你们本身去想吧。

ObjectWithLock::ObjectWithLock(ObjectWithLock const & rhs)
  : shared_res_((std::lock_guard<std::mutex>(rhs.mtx_), rhs.shared_res_))
{}

另外,这里ObjectWithLock只有一个资源shared_res_,若是有多个资源,解决方案相似,可是须要将多个资源先封装到一个结构中,而后将该结构应用到ObjectWithLock类中。

结束语

这里就只罗列的拷贝构造函数,移动构造函数同理,留给你们本身想吧。完!

相关文章
相关标签/搜索