C++類的特種函數(shù)生成機(jī)制詳解
C++類的特種函數(shù)生成機(jī)制
規(guī)則
參考Effective Morder C++上的說明:
- 默認(rèn)構(gòu)造函數(shù):僅當(dāng)類中不包含用戶聲明的構(gòu)造函數(shù)時(shí)才生成。
- 析構(gòu)函數(shù):默認(rèn)生成,當(dāng)基類的析構(gòu)函數(shù)為虛時(shí),派生類的默認(rèn)析構(gòu)函數(shù)為虛函數(shù)。
- 拷貝構(gòu)造函數(shù):僅當(dāng)類中不包含用戶聲明的拷貝構(gòu)造函數(shù)時(shí)才生成。如果該類聲明了移動(dòng)操作,那么拷貝構(gòu)造函數(shù)將被定義為刪除的。
- 拷貝賦值運(yùn)算符:僅當(dāng)類中不包含用戶聲明的拷貝賦值運(yùn)算符時(shí)才生成。如果該類聲明了移動(dòng)操作,那么拷貝賦值運(yùn)算符將被定義為刪除的。
- 移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符:僅當(dāng)類中不包含用戶聲明的拷貝操作、移動(dòng)操作和析構(gòu)函數(shù)時(shí)才生成。
例子:A BUG
因?yàn)椴皇煜の鰳?gòu)函數(shù)的生成機(jī)制,導(dǎo)致了一個(gè)BUG。
首先,下面的代碼沒有問題,因?yàn)閿?shù)據(jù)成員m_,所以Widget默認(rèn)也是個(gè)只移型別;mm中也可以插入一個(gè)由只移型別構(gòu)造的std::pair<int, Widget>,因?yàn)閜air默認(rèn)支持右值參數(shù)構(gòu)造(可以由只移的Widget構(gòu)造)和自身的移動(dòng)構(gòu)造函數(shù)(可以移動(dòng)構(gòu)造到unordered_map中):
class Widget {
public:
Widget() = default;
// ~Widget() = default;
private:
std::thread m_; // 只移型別
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()});
然后,我手賤加了一個(gè)默認(rèn)的析構(gòu)函數(shù):
class Widget {
public:
Widget() = default;
~Widget() = default;
private:
std::thread m_; // 只移型別
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!
報(bào)錯(cuò)信息極長,核心錯(cuò)誤是:
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的構(gòu)造單獨(dú)抽出來看到更清晰的報(bào)錯(cuò)信息:
// 代碼如下: make_pair(12, Widget()); // 報(bào)錯(cuò)如下: In template: no matching constructor for initialization of '__pair_type' (aka 'pair<int, Widget>')
“顯然”,是因?yàn)閃idget的移動(dòng)構(gòu)造函數(shù)被隱式刪除了(它既不能拷貝也不能移動(dòng)了),所以無法由Widget參數(shù)構(gòu)造一個(gè)std::pair。
解決方案就是不要定義析構(gòu)函數(shù),或者顯式定義一個(gè)移動(dòng)構(gòu)造函數(shù):
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
在我做試驗(yàn)的時(shí)候,一開始錯(cuò)把std::mutex記成了只移型別
定義了一個(gè)這樣的類:
class Widget {
public:
Widget() = default;
private:
std::mutex m_;
};
unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!
甚至在我沒有添加析構(gòu)函數(shù)的時(shí)候Widget就不能拷貝和移動(dòng)了。
看看源碼:
class mutex : private __mutex_base
{
public:
/* ... */
mutex() noexcept = default;
~mutex() = default;
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
/* ... */
}
顯然,因?yàn)閙utex自行定義了默認(rèn)的析構(gòu)函數(shù)而且把拷貝構(gòu)造函數(shù)定義為刪除的,那么它的移動(dòng)構(gòu)造函數(shù)也會(huì)被隱式刪除,所以mutex既不能拷貝也不能移動(dòng)。
和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定義了析構(gòu)函數(shù)和刪除的拷貝構(gòu)造函數(shù),但是它顯式定義了移動(dòng)構(gòu)造函數(shù),這使得它雖然不能拷貝但是可以移動(dòng)。
題外話:為什么std::mutex不可移動(dòng)?
大體來說就是std::mutex一般由多個(gè)線程調(diào)用,那么如果它的位置可以變化,那么怎么讓所有線程都知道它的新位置在哪里呢?
詳見stackoverflow: https://stackoverflow.com/questions/7557179/move-constructor-for-stdmutex
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C++ boost::asio編程-域名解析詳細(xì)介紹
這篇文章主要介紹了C++ boost::asio編程-域名解析詳細(xì)介紹的相關(guān)資料,這里附有實(shí)例代碼,幫助大家學(xué)習(xí)理解這部分知識(shí),需要的朋友可以參考下2016-11-11
C++中如何將operator==定義為類的成員函數(shù)
這篇文章主要介紹了C++中如何將operator==定義為類的成員函數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
C語言生成隨機(jī)數(shù)以及設(shè)置隨機(jī)數(shù)范圍的方法(超詳細(xì))
文章介紹了C語言中生成隨機(jī)數(shù)的方法,包括使用`rand`和`srand`函數(shù),以及如何通過`time`函數(shù)設(shè)置隨機(jī)種子以確保每次運(yùn)行程序生成的隨機(jī)數(shù)序列不同,此外,還詳細(xì)講解了如何根據(jù)需要設(shè)置隨機(jī)數(shù)的范圍,需要的朋友可以參考下2025-02-02

