zoukankan      html  css  js  c++  java
  • 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有默认构造函数的话。

    那么我们的拷贝构造怎么写呢?

    方案一

    非常有可能见到的一种解决方案是:

    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_。为什么这么说呢?

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

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

    成员变量的初始化顺序在C++里弄不好,就是各种坑。

    首先我们比较容易知道的是,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类中。

    结束语

    这里就只罗列的拷贝构造函数,移动构造函数同理,留给大家自己想吧。完!

  • 相关阅读:
    Hibernate——openSession和getCurrentSession区别
    Oracle存储过程分页 详解
    如何破解mysql数据库的密码
    Oracle约束详解
    Oracle分页查询
    Oracle如何管理权限和角色
    悲观锁和乐观锁详解
    Oracle控制结构详解
    Rose建模之各模型分析
    div布局中将页面居中的方法
  • 原文地址:https://www.cnblogs.com/wpcockroach/p/3917640.html
Copyright © 2011-2022 走看看