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類中。

結束語

這裏就只羅列的拷貝構造函數,移動構造函數同理,留給你們本身想吧。完!

相關文章
相關標籤/搜索