最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++特殊成員函數(shù)以及其生成機(jī)制詳解

 更新時(shí)間:2022年02月21日 09:26:22   作者:Shuo?Ouyang  
這篇文章主要給大家介紹了關(guān)于C++特殊成員函數(shù)以及其生成機(jī)制的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

前言

在C++中,特殊成員函數(shù)指的是那些編譯器在需要時(shí)會(huì)自動(dòng)生成的成員函數(shù)。C++98中有四種特殊的成員函數(shù),分別是默認(rèn)構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符。而在C++11中,隨著移動(dòng)語義的引入,移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符也加入了特殊成員函數(shù)的大家庭。本文主要基于Klaus Iglberger在CppCon 2021上發(fā)表的主題演講Back To Basics: The Special Member Fuctions以及Scott Meyers的著作Effective Modern C++中的條款17,向大家介紹這六種特殊成員函數(shù)的特點(diǎn)以及它們的生成機(jī)制。

默認(rèn)構(gòu)造函數(shù)

當(dāng)且僅當(dāng)以下條件成立時(shí),編譯器會(huì)生成一個(gè)默認(rèn)構(gòu)造函數(shù):

  • 沒有顯式聲明的構(gòu)造函數(shù)
  • 所有的數(shù)據(jù)成員和基類都擁有自己的默認(rèn)構(gòu)造函數(shù)

如果用戶聲明了自己的構(gòu)造函數(shù),那么編譯器就不會(huì)再去生成一個(gè)默認(rèn)構(gòu)造函數(shù);如果用戶沒有聲明構(gòu)造函數(shù),但是類中包含了一個(gè)沒有默認(rèn)構(gòu)造函數(shù)的數(shù)據(jù)成員,那么編譯器也不會(huì)生成默認(rèn)構(gòu)造函數(shù)。

數(shù)據(jù)成員初始化

編譯器生成默認(rèn)構(gòu)造函數(shù)會(huì)初始化所有類類型的數(shù)據(jù)成員,但是并不會(huì)初始化基礎(chǔ)類型的數(shù)據(jù)成員。以下面的代碼為例,第六行代碼會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù)將成員變量s初始化為空字符串,但是并不會(huì)初始化整型成員變量i以及指針pi。

struct Widget {
  int i;
  std::string s;
  int* pi;
};
int main() {
  Widget w1;   // Default initialization
  Widget w2{}; // Vaule initialization
  return 0;
}

如果我們想同時(shí)初始化所有的成員變量,可以使用值初始化,只需在聲明對象時(shí)添加一對大括號(hào)即可,見上述代碼第8行。如果沒有聲明默認(rèn)構(gòu)造函數(shù),值初始化會(huì)zero-initialize整個(gè)對象,然后default-initializes所有non-trivial的數(shù)據(jù)成員。以上面的代碼為例,使用值初始化后,i被初始化為0,s仍然被初始化為空字符串,而pi被初始化為nullptr。如果用戶聲明了默認(rèn)構(gòu)造函數(shù),那么值初始化就會(huì)按照用戶聲明來完成初始化操作。

通過默認(rèn)構(gòu)造函數(shù),我們可以初始化類中的數(shù)據(jù)成員。但是需要注意賦值和初始化的區(qū)別。在下面的代碼中,我們實(shí)現(xiàn)了兩個(gè)默認(rèn)構(gòu)造函數(shù)(僅僅為了說明賦值和初始化的區(qū)別,不代表類中能夠?qū)崿F(xiàn)兩個(gè)默認(rèn)構(gòu)造函數(shù))。在第一個(gè)默認(rèn)構(gòu)造函數(shù)中,所有的成員在函數(shù)體內(nèi)執(zhí)行賦值操作。對于基礎(chǔ)類型來說還好,但是對于類類型或者std::string這種,一次賦值操作帶來的開銷要比初始化的開銷大。而第二個(gè)默認(rèn)構(gòu)造函數(shù)使用了成員初始化列表,每次操作都是初始化,所以它的開銷會(huì)更低,性能也更好。

struct Widget {
  Widget() {
    i = 42;       // Assignment, not initialization
    s = "CppCon"; // Assignment, not initialization
    pi = nullptr; // Assignment, not initialization
  }

  Widget()
    : i{42}       // Initializing to 42
    , s{"CppCon"} // Initializing to "CppCon"
    , pi{}        // Initializing to nullptr
   {}

  int i;
  std::string s;
  int* pi;
};

對于數(shù)據(jù)成員的初始化,C++ Core Guideline定義了兩條規(guī)則。首先,我們要按照數(shù)據(jù)成員在類中的定義順序來初始化數(shù)據(jù)成員;其次,盡量在構(gòu)造函數(shù)中使用初始化而非賦值。

Core Guideline C.47: Define and initialize member variables in the order of member declaration.

Core Guideline C.49: Prefer initialization to assignment in constructors.

析構(gòu)函數(shù)

當(dāng)用戶沒有顯式聲明析構(gòu)函數(shù)時(shí),編譯器會(huì)生成一個(gè)析構(gòu)函數(shù)。編譯器生成的析構(gòu)函數(shù)會(huì)調(diào)用類類型成員變量的析構(gòu)函數(shù),但是不會(huì)對基礎(chǔ)類型的成員變量執(zhí)行任何操作。如果類中含有指針類型的成員變量,那么編譯器生成的析構(gòu)函數(shù)就有可能導(dǎo)致資源泄露,因?yàn)榫幾g器生成的析構(gòu)函數(shù)并不會(huì)釋放掉指針?biāo)赶虻哪切┵Y源。

因此,如果類中的數(shù)據(jù)成員擁有某些外部資源的所有權(quán),我們就需要實(shí)現(xiàn)一個(gè)析構(gòu)函數(shù)來正確釋放掉相關(guān)資源。如果確實(shí)沒有啥資源需要手動(dòng)釋放,那么也不要寫一個(gè)空的析構(gòu)函數(shù),最好是讓編譯器生成或者將析構(gòu)函數(shù)定義成=default。

拷貝操作

我們首先來看一下拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符的函數(shù)簽名。一般來說,拷貝構(gòu)造函數(shù)的形參是一個(gè)常量左值引用,極少數(shù)情況下是一個(gè)非常量左值引用,但不可能是一個(gè)對象的拷貝,因?yàn)檫@會(huì)導(dǎo)致遞歸調(diào)用。對于拷貝賦值運(yùn)算符,它的形參也是一個(gè)常量左值引用,極少數(shù)情況下是非常量左值引用,也有可能是一個(gè)對象的拷貝,因?yàn)榭截愘x值運(yùn)算符可以通過拷貝構(gòu)造函數(shù)實(shí)現(xiàn),所以這種形參是合法的。

// copy constructor
Widget(const Weidget&); // The default
Widget(Widget&);        // Possible, but very likely not reasonable
Widget(Widget);         // Not possible, recursive call

// copy assignment operator
Widget& operator=(const Widget&); // The default
Widget& operator=(Widget&);       // Possible, but very likely not reasonable
Widget& operator=(Widget);        // Reasonable, builds on the copy constructor

當(dāng)且僅當(dāng)以下條件成立時(shí),編譯器會(huì)生成拷貝操作:

  • 不存在顯式聲明的拷貝操作
  • 不存在顯式聲明的移動(dòng)操作
  • 所有的成員變量都能夠被拷貝構(gòu)造或拷貝賦值

拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符的生成是獨(dú)立的:聲明了其中一個(gè),并不會(huì)阻止編譯器生成另一個(gè)。如果用戶聲明了拷貝構(gòu)造函數(shù),但是沒有聲明拷貝賦值運(yùn)算符,同時(shí)又編寫了要求拷貝賦值的代碼,那么編譯器就會(huì)自動(dòng)生成拷貝賦值運(yùn)算符,反之亦然。

編譯器生成的拷貝操作默認(rèn)會(huì)按成員進(jìn)行拷貝。對于指針類型的數(shù)據(jù)成員,如果執(zhí)行按成員拷貝,那么就只會(huì)拷貝成員的值,也就是拷貝指針的值。這樣一來,就會(huì)有兩個(gè)對象指向同一塊資源。當(dāng)其中一個(gè)對象被析構(gòu)以后,資源會(huì)被釋放,另一個(gè)對象中的指針就成了懸掛指針(Dangling Pointer)。當(dāng)這個(gè)對象被析構(gòu)時(shí),它所指向的資源就會(huì)被析構(gòu)兩次,內(nèi)存的重復(fù)釋放會(huì)導(dǎo)致嚴(yán)重的錯(cuò)誤。為了解決此問題,我們需要在拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符中執(zhí)行深拷貝操作,也就是要拷貝指針指向的那一塊資源。

struct Widget {
  Widget(Wiget& other) noexcept
    : Base{other}
    , i{other.i}
    , s{other.s}
    , pr{other.pr ? new Resource(*ohter.pr) : nullptr}
  {}

  Widget& operator=(Widget&& other) {
    deleter pr; // cleanup current resource
    Base::operator=(std::move(other));
    i = other.i;
    s = other.s;
    pr = other.pr ? new Resource{*other.pr} : nullptr;
    return *this;
  }
  int i;
  std::string s;
  Resource* pr{};
};

注意在上述代碼的拷貝賦值運(yùn)算符中,我們首先刪除了當(dāng)前對象所指向的資源,然后再執(zhí)行相關(guān)的拷貝操作。然而,這會(huì)導(dǎo)致程序不能正確處理self-assignment的情況。形如Widget w{}; w = w;這樣的代碼就會(huì)釋放掉對象w指向的資源,從而導(dǎo)致程序發(fā)生錯(cuò)誤。幸運(yùn)的是,我們可以用copy-and-swap的思想,通過一個(gè)臨時(shí)對象和swap函數(shù)來解決此問題。臨時(shí)對象在退出作用域是會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),所以我們就不用擔(dān)心資源泄漏的問題。

Widget& operator=(const Widget& other) {
  Widget tmp(other);
  swap(tmp);
  return *this;
}
void swap(Widget& other) {
  std::swap(id, other.id);
  std::swap(name, other.name);
  std::swap(pr, other.pr);
}

這種做法的好處就是安全,代碼能正確處理self-assignment的情況,但它的缺點(diǎn)就是性能比較一般。

移動(dòng)操作

我們首先來看一下移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符的函數(shù)簽名。一般來說,移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符的形參都是一個(gè)右值引用,帶有const的形參是合法的,但是非常少見,一般也不會(huì)遇到。

// move constructor
Widget(Widget&&) noexcept;      // The default
Widget(const Widget&&) noexcept // Possible, but uncommon

// move assignment operator
Widget& operator=(Widget&&) noexcept;      // The default
Widget& operator=(const Widget&&) noexcept // Possible, but uncommon

當(dāng)且僅當(dāng)以下條件成立時(shí),編譯器會(huì)生成移動(dòng)操作:

  • 不存在顯式聲明的移動(dòng)操作
  • 不存在顯式聲明的析構(gòu)函數(shù)和拷貝操作
  • 所有的數(shù)據(jù)成員都是可以被拷貝或移動(dòng)

移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符的生成并不獨(dú)立:聲明了其中一個(gè),編譯器就不會(huì)生成另一個(gè)。這樣做的原因是,如果用戶聲明了一個(gè)移動(dòng)構(gòu)造函數(shù),那么這就表明移動(dòng)操作的行為將會(huì)與編譯器所生成的移動(dòng)構(gòu)造函數(shù)不一致。而若是按成員進(jìn)行的移動(dòng)操作有不合理之處,那么按成員移動(dòng)的賦值運(yùn)算符極有可能同樣有不合理之處。因此,聲明移動(dòng)構(gòu)造函數(shù)會(huì)阻止編譯器生成移動(dòng)賦值運(yùn)算符,反之亦然。

與拷貝操作類似,編譯器生成的移動(dòng)操作默認(rèn)會(huì)按成員進(jìn)行移動(dòng)。顯然,如果數(shù)據(jù)成員是一個(gè)指針類型,那么按成員移動(dòng)同樣將會(huì)導(dǎo)致懸掛指針。所以,對于包含指針類型的類,我們需要按照下面的方式實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符,其中std::exchange(a, b)的作用是用b的值去替換a的值并返回a的舊值。

struct Widget {
  Widget(Wiget&& other) noexcept
    : Base{std::move(other)}
    , i{std::move(other.i)}
    , s{std::move(other.s)}
    , pr{std::exchange(other.pr, {})}
  {}

  Widget& operator=(Widget&& other) {
    deleter pr;
    Base::operator=(std::move(other));
    i = std::move(other.i);
    s = std::move(other.s);
    pr = std::exchange(other.pr, {});
  }
  int i;
  std::string s;
  Resource* pr{};
};

然而,上面這種實(shí)現(xiàn)方式同樣無法處理self-assignment的問題。雖然移動(dòng)一個(gè)對象到它本身是一件非常奇怪的事情,一般也不會(huì)有人去寫這種代碼,但是作為類的提供者,我們必須要盡量考慮到所有可能出現(xiàn)的情況。對于self-assignment這個(gè)問題,我們可以借助copy-and-swap思想,利用一個(gè)臨時(shí)對象來解決,代碼如下。

Widget& operator=(Widget&& other) noexcept {
  Widget tmp(std::move(other));
  swap(tmp);
  return *this;
}
~Widget() { delete pr; }

使用原生指針來管理資源會(huì)讓我們的代碼寫起來比較困難和繁瑣。如果我們用智能指針替換掉原生指針,那么代碼寫起來將會(huì)容易很多。如果我們使用unique_ptr替換掉上例中的原生指針,因?yàn)閡nique_ptr只能被移動(dòng)不能被拷貝,所以我們只需要實(shí)現(xiàn)拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符(如果我們真的需要拷貝操作的話),并將默認(rèn)構(gòu)造函數(shù)、析構(gòu)函數(shù)和移動(dòng)操作聲明為=default即可。如果我們使用shared_ptr,那么連拷貝操作也不用寫了,六個(gè)特殊成員函數(shù)群都定義成=default就完事了,不過shared_ptr會(huì)改變整個(gè)類的語義,因?yàn)樗械闹羔樁紩?huì)指向同一個(gè)資源,所以在用它的時(shí)候要多加小心。C++ Core Guideline就指出,盡量用unique_ptr而非shared_ptr,除非你是真的想共享資源的所有權(quán)。

Core Guideline R.21: Prefer unique_ptr over shared_ptr unless you need to share ownership.

最后,我們再來看下C++ Core Guideline中的The Rule of Zero以及The Rule of Five。這兩條規(guī)則的意思非常簡單,就是說我們在定義一個(gè)類的時(shí)候,如果能避免定義所有的默認(rèn)操作,那就盡量不定義;如果定義或刪除了某個(gè)默認(rèn)操作,那么就定義或刪除所有的默認(rèn)操作。

Core Guideline C.20: If you can avoid defining default operation, do (aka The Rule of Zero).

Core Guideline C.21: If you define or =delete any default operation, define or =delete them all (aka The Rule of Five).

總結(jié) 

到此這篇關(guān)于C++特殊成員函數(shù)以及其生成機(jī)制的文章就介紹到這了,更多相關(guān)C++特殊成員函數(shù)及生成機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++中訪問權(quán)限的示例詳解

    C++中訪問權(quán)限的示例詳解

    C++通過 public、protected、private 三個(gè)關(guān)鍵字來控制成員變量和成員函數(shù)的訪問權(quán)限(也稱為可見性),下面這篇文章主要給大家介紹了關(guān)于C++中訪問權(quán)限的相關(guān)資料,需要的朋友可以參考下
    2021-07-07
  • OpenCV圖像特征提取之Shi-Tomasi角點(diǎn)檢測算法詳解

    OpenCV圖像特征提取之Shi-Tomasi角點(diǎn)檢測算法詳解

    Harris角點(diǎn)檢測算法就是對角點(diǎn)響應(yīng)函數(shù)R進(jìn)行閾值處理,Shi-Tomasi原理幾乎和Harris一樣的,只不過最后計(jì)算角點(diǎn)響應(yīng)的公式發(fā)生了變化。本文將和大家詳細(xì)說說Shi-Tomasi角點(diǎn)檢測算法的原理與實(shí)現(xiàn),需要的可以參考一下
    2022-09-09
  • CString,字符串,整數(shù)等相互轉(zhuǎn)換方法(推薦)

    CString,字符串,整數(shù)等相互轉(zhuǎn)換方法(推薦)

    下面小編就為大家?guī)硪黄狢String,字符串,整數(shù)等相互轉(zhuǎn)換方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-09-09
  • C++處理鍵盤輸入的方法

    C++處理鍵盤輸入的方法

    這篇文章主要介紹了C++處理鍵盤輸入的方法,是C++程序設(shè)計(jì)中非常實(shí)用的技巧,需要的朋友可以參考下
    2014-10-10
  • C++面經(jīng)之什么是RAII面試問題解析

    C++面經(jīng)之什么是RAII面試問題解析

    這篇文章主要介紹了C++面經(jīng)之什么是RAII面試問題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • C++11中的chrono庫詳解

    C++11中的chrono庫詳解

    C++11提供了日期時(shí)間相關(guān)的庫chrono,通過chrono庫可以很方便的處理日期和時(shí)間,這篇文章主要介紹了C++11中的chrono庫,需要的朋友可以參考下
    2023-03-03
  • 如何利用tinyxml操縱xml及注意問題

    如何利用tinyxml操縱xml及注意問題

    這篇博客,我們詳細(xì)講述如何利用tinyxml操縱xml。以及在操作的過程中,我們應(yīng)該注意的問題
    2013-01-01
  • VScode運(yùn)行C++中文終端亂碼的解決方案

    VScode運(yùn)行C++中文終端亂碼的解決方案

    這篇文章主要介紹了VScode運(yùn)行C++中文終端亂碼的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Species Tree 利用HashTable實(shí)現(xiàn)實(shí)例代碼

    Species Tree 利用HashTable實(shí)現(xiàn)實(shí)例代碼

    這篇文章主要介紹了Species Tree 利用HashTable實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • C語言 淺談棧與隊(duì)列的定義與操作

    C語言 淺談棧與隊(duì)列的定義與操作

    棧和隊(duì)列,嚴(yán)格意義上來說,也屬于線性表,因?yàn)樗鼈円捕加糜诖鎯?chǔ)邏輯關(guān)系為 "一對一" 的數(shù)據(jù),但由于它們比較特殊,因此將其單獨(dú)作為一章,做重點(diǎn)講解
    2021-11-11

最新評論

平邑县| 洛南县| 广昌县| 清远市| 中西区| 洞口县| 逊克县| 奉节县| 扬州市| 长宁县| 大新县| 南召县| 冀州市| 巴东县| 迁西县| 内黄县| 康定县| 大城县| 沁水县| 汝南县| 永宁县| 综艺| 黎川县| 布拖县| 元阳县| 马关县| 绿春县| 嘉鱼县| 揭东县| 潢川县| 河津市| 定结县| 图木舒克市| 辽阳县| 凉山| 白玉县| 忻城县| 南汇区| 贡觉县| 商都县| 平原县|