C++類的特種函數生成機制詳解

C++類的特種函數生成機制

規則

參考Effective Morder C++上的說明:

  • 默認構造函數:僅當類中不包含用戶聲明的構造函數時才生成。
  • 析構函數:默認生成,當基類的析構函數為虛時,派生類的默認析構函數為虛函數。
  • 拷貝構造函數:僅當類中不包含用戶聲明的拷貝構造函數時才生成。如果該類聲明瞭移動操作,那麼拷貝構造函數將被定義為刪除的。
  • 拷貝賦值運算符:僅當類中不包含用戶聲明的拷貝賦值運算符時才生成。如果該類聲明瞭移動操作,那麼拷貝賦值運算符將被定義為刪除的。
  • 移動構造函數和移動賦值運算符:僅當類中不包含用戶聲明的拷貝操作、移動操作和析構函數時才生成。

例子:A BUG

因為不熟悉析構函數的生成機制,導致瞭一個BUG。

首先,下面的代碼沒有問題,因為數據成員m_,所以Widget默認也是個隻移型別;mm中也可以插入一個由隻移型別構造的std::pair<int, Widget>,因為pair默認支持右值參數構造(可以由隻移的Widget構造)和自身的移動構造函數(可以移動構造到unordered_map中):

class Widget {
public:
    Widget() = default;
//    ~Widget() = default;
private:
    std::thread m_; // 隻移型別
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()});

然後,我手賤加瞭一個默認的析構函數:

class Widget {
public:
    Widget() = default;
    ~Widget() = default;
private:
    std::thread m_; // 隻移型別
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!

報錯信息極長,核心錯誤是:

error: no matching function for call to ‘std::unordered_map<int, Widget>::insert(<brace-enclosed initializer list>)'
   45 |     unordered_map<int, Widget> mm;

可以把std::pair的構造單獨抽出來看到更清晰的報錯信息:

// 代碼如下:
make_pair(12, Widget());
// 報錯如下:
In template: no matching constructor for initialization of '__pair_type' (aka 'pair<int, Widget>')

“顯然”,是因為Widget的移動構造函數被隱式刪除瞭(它既不能拷貝也不能移動瞭),所以無法由Widget參數構造一個std::pair。

解決方案就是不要定義析構函數,或者顯式定義一個移動構造函數:

class Widget {
public:
    Widget() = default;
    Widget(Widget&&) = default;
    ~Widget() = default;
private:
    std::thread m_; // 隻移型別
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()});

例子:std::mutex和std::thread

在我做試驗的時候,一開始錯把std::mutex記成瞭隻移型別

定義瞭一個這樣的類:

class Widget {
public:
    Widget() = default;
private:
    std::mutex m_;
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!

甚至在我沒有添加析構函數的時候Widget就不能拷貝和移動瞭。

看看源碼:

class mutex : private __mutex_base
{
  public:
    /* ... */
    mutex() noexcept = default;
    ~mutex() = default;
    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;
	  /* ... */
}

顯然,因為mutex自行定義瞭默認的析構函數而且把拷貝構造函數定義為刪除的,那麼它的移動構造函數也會被隱式刪除,所以mutex既不能拷貝也不能移動。

和std::thread源碼比較一下:

class thread
{
  public:
  thread() noexcept = default;
  thread(const thread&) = delete;
  thread(thread&& __t) noexcept
  { 
    swap(__t);
  }
  ~thread()
  {
    if (joinable())
      std::terminate();
  }
}

雖然std::thread定義瞭析構函數和刪除的拷貝構造函數,但是它顯式定義瞭移動構造函數,這使得它雖然不能拷貝但是可以移動。

題外話:為什麼std::mutex不可移動?

大體來說就是std::mutex一般由多個線程調用,那麼如果它的位置可以變化,那麼怎麼讓所有線程都知道它的新位置在哪裡呢?

詳見stackoverflow: https://stackoverflow.com/questions/7557179/move-constructor-for-stdmutex

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: