C++中類的拷貝控制

C++中類的拷貝控制

轉自:https://www.cnblogs.com/ronny/p/3734110.html

1,什么是類的拷貝控制

當我們定義一個類的時候,為了讓我們定義的類類型像內置類型(char,int,double等)一樣好用,我們通常需要考下面幾件事:

Q1:用這個類的對象去初始化另一個同類型的對象。

Q2:將這個類的對象賦值給另一個同類型的對象。

Q3:讓這個類的對象有生命周期,比如局部對象在代碼部結束的時候,需要銷毀這個對象。

因此C++就定義了5種拷貝控制操作,其中2個移動操作是C++11標準新加入的特性:

拷貝構造函數(copy constructor)

移動構造函數(move constructor)

拷貝賦值運算符(copy-assignment operator)

移動賦值運算符(move-assignment operator)

析構函數 (destructor)

前兩個構造函數發生在Q1時,中間兩個賦值運算符發生在Q2時,而析構函數則負責類對象的銷毀。

但是對初學者來說,既是福音也是災難的是,如果我們沒有在定義的類里面定義這些控制操作符,編譯器會自動的為我們合成一個版本。這有時候看起來是好事,但是編譯器不是萬能的,它的行為在很多時候并不是我們想要的。

所以,在實現拷貝控制操作中,最困難的地方是認識到什么時候需要定義這些操作

2,拷貝構造函數

拷貝構造函數是構造函數之一,它的參數是自身類類型的引用,且如果有其他參數,則任何額外的參數都有默認值。

class Foo{ 
public: Foo(); Foo(const Foo&); 
};

我們從上面代碼中可以注意到幾個問題:

1,我們把形參定義為const類型,雖然我們也可以定義非const的形參,但是這樣做基本上沒有意義的,因為函數的功能只涉及到成員的復制操作。

2,形參是本身類類型的引用,而且必須是引用類型。為什么呢?

我們知道函數實參與形參之間的值傳遞,是通過拷貝完成的。那么當我們將該類的對象傳遞給一個函數的形參時,會調用該類的拷貝構造函數,而拷貝構造函數本身也是一個函數,因為是值傳遞而不是引用,在調用它的時候也需要調用類的拷貝構造函數(它自身),這樣無限循環下去,無法完成。

3,拷貝構造函數通過不是explict的。

如果我們沒有定義拷貝構造函數,編譯器會為我們定義一個,這個函數會從給定的對象中依次將每個非static成員拷貝到正在創建的對象中。成員自身的類型決定了它是如何被拷貝的:類類型的成員,會使用其拷貝構造函數來拷貝;內置類型則直接拷貝;數組成員會逐元素地拷貝

區分直接初始化與拷貝初始化:

string name("name_str");        //直接初始化 
string name = string("name_str");    // 拷貝初始化 
string name = "name_str";        // 拷貝初始化

直接初始化是要求編譯器使用普通的函數匹配來選擇與我們提供的參數最匹配的構造函數;當我們使用拷貝初始化時,我們要求編譯器將右側運算對象拷貝到正在創建的對象中,如果需要的話還要進行類型轉換(第三行代碼隱藏了一個C風格字符串轉換為string類型)

3,拷貝賦值運算符

拷貝賦值運算符是一個對賦值運算符的重載函數,它返回左側運算對象的引用。

class Foo 
{ 
public: Foo& operator=(const Foo&); 
};

與拷貝構造函數一樣,如果沒有給類定義拷貝賦值運算符,編譯器將為它合成一個。

4,析構函數

析構函數是由波浪線接類名構成,它沒有返回值,也不接受參數。因為沒有參數,所以它不存在重載函數,也就是說一個類只有一個析構函數。

析構函數做的事情與構造函數相反,那么我們先回憶一個構造函數都做了哪些事:

1,按成員定義的順序創建每個成員。

2,根據成員初始化列表初始化每個成員。

3,執行構造函數函數體。

而析構函數中不存在類似構造函數中初始化列表的東西來控制成員如何銷毀,析構部分是隱式的。成員如何銷毀依賴于成員自身的類型,如果是類類型則調用本身的析構函數,如果是內置類型則會自動銷毀。而如果是一個指針,則需要手動的釋放指針指向的空間。與普通指針不同的是,智能指針是一個類,它有自己的析構函數。

那么什么時候會調用析構函數呢?在對象銷毀的時候:

  • 變量在離開其作用域時被銷毀;
  • 當一個對象被銷毀時,其成員被銷毀。
  • 容器被銷毀時,成員被銷毀。
  • 對于動態分配的對象,當對指向它的指針應用delete運算符時被銷毀。
  • 對于臨時對象,當創建它的賽事表達式結束時被銷毀。

值得注意的析構函數是自動運行的。析構函數的函數體并不直接銷毀成員,成員是在析構函數體之后隱含的析構階段中被銷毀的。在整個對象銷毀過程中,析構函數體是作為成員銷毀步驟之外的另一部分而進行的。

5,定義拷貝控制操作的原則

在第1點里有提過,在定義類的時候處理拷貝控制最困難的在于什么時候需要自己定義,什么時候讓編譯器自己合成。

那么我們可以有下面2點原則:

如果一個類需要定義析構函數,那么幾乎可以肯定它也需要一個拷貝構造函數和一個拷貝賦值函數,反過來不一定成立。

如果一個類需要一個拷貝構造函數,幾乎可以肯定它也需要一個拷貝賦值函數,反之亦然。

為什么析構函數與拷貝構造函數與賦值函數關系這么緊密呢,或者說為什么我們在討論拷貝控制(5種)的時候要把析構函數一起放進來呢?

首先,我們思考什么時候我們一定要自己來定義析構函數,比如:類里面有動態分配內存。

class HasPtr 
{ 
public: HasPtr(const string&s = string()) :ps(new string(s), i(0)){} ~HasPtr(){ delete ps; } 
private: int i; string* ps; 
};

我們知道如果是編譯器自動合成的析構函數,則不會去delete指針變量的,所以ps指向的內存將無法釋放,所以一個主動定義的析構函數是需要的。那么如果沒有給這個類定義拷貝構造函數和拷貝賦值函數,將會怎么樣?

編譯器自動合成的版本,將簡單的拷貝指針成員,這意味著多個HasPtr對象可能指向相同的內存。

HasPtr p("some values"); 
f(p);        // 當f結束時,p.ps指向的內存被釋放 
HasPtr q(p);// 現在p和q都指向無效內存

6,使用=default和=delete

我們可以使用=default來顯式地要求編譯器生成合成的版本。合成的函數將隱式地聲明為內聯的,如果我們不希望合成的成員是內聯的,應該只對成員的類外定義使用=default。

有的時候我們定義的某些類不需要拷貝構造函數和拷貝賦值運算符,比如iostream類就阻止拷貝,以避免多個對象寫入或讀取相同的IO緩沖。

新的標準里,我們可以在拷貝構造函數和拷貝賦值運算符函數的參數列表后面加上=delete用來指出我們希望將它定義為刪除的,這樣的函數稱為刪除函數。

class NoCopy 
{ NoCopy() = default;    // 使用合成的默認構造函數 NoCopy(const NoCopy&) = delete;        // 刪除拷貝 NoCopy& operator=(const NoCopy&) = delete;    // 刪除賦值 ~NoCopy() = default;    // 使用合成的析構函數 
};

注意:析構函數不能是刪除的成員,因為這樣的類是無法銷毀的。

如果一個類有const成員或者有引用成員,則這個類合成拷貝賦值運算符是被定義為刪除的。

在新的標準出來之前,類是通過將其拷貝構造函數的拷貝賦值運算符聲明為private來阻止拷貝,而且為了防止成員被友元或其他成員訪問,會對這些成員函數只聲明,但不定義。

7,右值引用

所謂的右值引用就是必須綁定在右值上的引用,我們可以通過&&來獲得右值引用,右值引用一個很重要的性質是只能綁定到一個將要銷毀的對象,所以我們可以自由地將一個右值引用的資源“移動”到另一個對象中。

我們可以將一個右值引用綁定到表達式上,但不能將右值引用綁定到一個左值上:

int i = 42; 
int &r = i;        // 正確:r引用i 
int &&rr = i;    // 錯誤:不能將一個右值引用綁定到一個左值上 
int &r2 = i * 42;    // i*42是一具右值 
const int& r3 = i * 42;    // 可以將一個const的引用綁定到一個右值上 
int && rr2 = i * 42;    // 正確:將rr2綁定到乘法結果上

總體來說:左值有持久的狀態,而右值要么是字面常量,要么是表達式求值過程中創建的臨時對象。

從而我們得知,關于右值引用:1)所引用的對象將要銷毀;2)該對象沒有其他用戶。

標準庫提供了一個std::move函數,讓我們可以獲得左值上的右值引用:

int  &&r3 = std::move(rr1); // rr1是一個變量

move調用告訴編譯器:我們有一個左值,但是我們希望像一個右值一個處理它。在上面的代碼后,要么銷毀rr1,要么對rr1進行賦值,否則我們不能使用rr1。

另外一點值得注意的是,我們使用std::move而不是move,即使我們提供了using聲明。

8,移動構造函數和移動賦值運算符

與拷貝一樣,移動操作同樣發生在我們一個類的對象去初始化或賦值同一個類類型的對象時,但是與拷貝不同的是,對象的內容實際上從源對象移動到了目標對象,而源對象丟失了內容。移動操作一般只發生在當這個源對象是一個uname的對象的時候。

一個uname object意思是一個臨時對象,還沒有被賦予一個名字,例如一個返回該類型的函數返回值或者一個類型轉換操作返回的對象。

MyClass fn();            // function returning a MyClass object
MyClass foo;             // default constructor
MyClass bar = foo;       // copy constructor
MyClass baz = fn();      // move constructor
foo = bar;               // copy assignment
baz = MyClass();         // move assignment 

上面的代碼中由fn()返回的對象和由MyClass構造出來的對象都是unnamed,用這樣的對象給MyClass賦值或初始化時,并不需要拷貝,因為源對象只有很短的生命周期。

移動構造函數與移動賦值函數的定義形式上與拷貝操作一樣,只是將拷貝函數的形參的引用換成右值引用。

MyClass (MyClass&&);             // move-constructor
MyClass& operator= (MyClass&&);  // move-assignment

移動操作對那些需要管理存儲空間的類是非常有用的,比如我們下面定義的這個類

// move constructor/assignment
#include <iostream>
#include <string>
using namespace std;class Example6 {string* ptr;public:Example6 (const string& str) : ptr(new string(str)) {}~Example6 () {delete ptr;}// move constructorExample6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;}// move assignmentExample6& operator= (Example6&& x) {delete ptr; ptr = x.ptr;x.ptr=nullptr;return *this;}// access content:const string& content() const {return *ptr;}// addition:Example6 operator+(const Example6& rhs) {return Example6(content()+rhs.content());}
};int main () {Example6 foo ("Exam");Example6 bar = Example6("ple");   // move-constructionfoo = foo + bar;                  // move-assignmentcout << "foo's content: " << foo.content() << '\n';return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532576.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532576.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532576.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

centos7ubuntu搭建Vulhub靶場(推薦Ubuntu)

這里寫目錄標題一.前言總結二.成功操作&#xff1a;三.出現報錯&#xff1a;四.vulhub使用正文&#xff1a;一.前言總結二.成功操作&#xff1a;三.出現報錯&#xff1a;四.vulhub使用看完點贊關注不迷路!!!! 后續繼續更新優質安全內容!!!!!一.前言總結 二.成功操作&#xff1…

使用 PyTorch 數據讀取,JAX 框架來訓練一個簡單的神經網絡

使用 PyTorch 數據讀取&#xff0c;JAX 框架來訓練一個簡單的神經網絡 本文例程部分主要參考官方文檔。 JAX簡介 JAX 的前身是 Autograd &#xff0c;也就是說 JAX 是 Autograd 升級版本&#xff0c;JAX 可以對 Python 和 NumPy 程序進行自動微分。可以通過 Python的大量特征…

Yapi Mock 遠程代碼執行漏洞

跟風一波復現Yapi 漏洞描述&#xff1a; YApi接口管理平臺遠程代碼執行0day漏洞&#xff0c;攻擊者可通過平臺注冊用戶添加接口&#xff0c;設置mock腳本從而執行任意代碼。鑒于該漏洞目前處于0day漏洞利用狀態&#xff0c;強烈建議客戶盡快采取緩解措施以避免受此漏洞影響 …

C++ ACM模式輸入輸出

C ACM模式輸入輸出 以下我們都以求和作為題目要求&#xff0c;來看一下各種輸入輸出應該怎么寫。 1 只有一個或幾個輸入 輸入樣例&#xff1a; 3 5 7輸入輸出模板&#xff1a; int main() {int a, b, c;// 接收有限個輸入cin >> a >> b >> c;// 輸出結果…

CVE-2017-10271 WebLogic XMLDecoder反序列化漏洞

漏洞產生原因&#xff1a; CVE-2017-10271漏洞產生的原因大致是Weblogic的WLS Security組件對外提供webservice服務&#xff0c;其中使用了XMLDecoder來解析用戶傳入的XML數據&#xff0c;在解析的過程中出現反序列化漏洞&#xff0c;導致可執行任意命令。攻擊者發送精心構造的…

樹莓派攝像頭 C++ OpenCV YoloV3 實現實時目標檢測

樹莓派攝像頭 C OpenCV YoloV3 實現實時目標檢測 本文將實現樹莓派攝像頭 C OpenCV YoloV3 實現實時目標檢測&#xff0c;我們會先實現樹莓派對視頻文件的逐幀檢測來驗證算法流程&#xff0c;成功后&#xff0c;再接入攝像頭進行實時目標檢測。 先聲明一下筆者的主要軟硬件配…

【實戰】記錄一次服務器挖礦病毒處理

信息收集及kill&#xff1a; 查看監控顯示長期CPU利用率超高&#xff0c;懷疑中了病毒 top 命令查看進程資源占用&#xff1a; netstat -lntupa 命令查看有無ip進行發包 netstat -antp 然而并沒有找到對應的進程名 查看java進程和solr進程 ps aux &#xff1a;查看所有進程…

ag 搜索工具參數詳解

ag 搜索工具參數詳解 Ag 是類似ack&#xff0c; grep的工具&#xff0c;它來在文件中搜索相應關鍵字。 官方列出了幾點選擇它的理由&#xff1a; 它比ack還要快 &#xff08;和grep不在一個數量級上&#xff09;它會忽略.gitignore和.hgignore中的匹配文件如果有你想忽略的文…

CVE-2013-4547 文件名邏輯漏洞

搭建環境&#xff0c;訪問 8080 端口 漏洞說明&#xff1a; Nginx&#xff1a; Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件&#xff08;IMAP/POP3&#xff09;代理服務器&#xff0c;在BSD-like 協議下發行。其特點是占有內存少&#xff0c;并發能力強&#xf…

CMake指令入門 ——以構建OpenCV項目為例

CMake指令入門 ——以構建OpenCV項目為例 轉自&#xff1a;https://blog.csdn.net/sandalphon4869/article/details/100589747 一、安裝 sudo apt-get install cmake安裝好后&#xff0c;輸入 cmake -version如果出現了cmake的版本顯示&#xff0c;那么說明安裝成功 二、c…

CVE-2017-7529Nginx越界讀取緩存漏洞POC

漏洞影響 低危&#xff0c;造成信息泄露&#xff0c;暴露真實ip等 實驗內容 漏洞原理 通過查看patch確定問題是由于對http header中range域處理不當造成&#xff0c;焦點在ngx_http_range_parse 函數中的循環&#xff1a; HTTP頭部range域的內容大約為Range: bytes4096-81…

Linux命令行性能監控工具大全

Linux命令行性能監控工具大全 作者&#xff1a;Arnold Lu 原文&#xff1a;https://www.cnblogs.com/arnoldlu/p/9462221.html 關鍵詞&#xff1a;top、perf、sar、ksar、mpstat、uptime、vmstat、pidstat、time、cpustat、munin、htop、glances、atop、nmon、pcp-gui、collect…

Weblogic12c T3 協議安全漏洞分析【CVE-2020-14645 CVE-2020-2883 CVE-2020-14645】

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 關注公眾號&#xff1a;b1gpig信息安全&#xff0c;文章推送不錯過 ## 前言 WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基于JAV…

Getshell總結

按方式分類&#xff1a; 0x01注入getshell&#xff1a; 0x02 上傳 getwebshell 0x03 RCE getshell 0x04 包含getwebshell 0x05 漏洞組合拳getshell 0x06 系統層getcmdshell 0x07 釣魚 getcmdshell 0x08 cms后臺getshell 0x09 紅隊shell競爭分析 0x01注入getshell&#xff1a;…

編寫可靠bash腳本的一些技巧

編寫可靠bash腳本的一些技巧 原作者&#xff1a;騰訊技術工程 原文鏈接&#xff1a;https://zhuanlan.zhihu.com/p/123989641 寫過很多 bash 腳本的人都知道&#xff0c;bash 的坑不是一般的多。 其實 bash 本身并不是一個很嚴謹的語言&#xff0c;但是很多時候也不得不用。以下…

python 到 poc

0x01 特殊函數 0x02 模塊 0x03 小工具開發記錄 特殊函數 # -*- coding:utf-8 -*- #內容見POC.demo; POC.demo2 ;def add(x,y):axyprint(a)add(3,5) print(------------引入lambad版本&#xff1a;) add lambda x,y : xy print(add(3,5)) #lambda函數,在lambda函數后面直接…

protobuf版本常見問題

protobuf版本常見問題 許多軟件都依賴 google 的 protobuf&#xff0c;我們很有可能在安裝多個軟件時重復安裝了多個版本的 protobuf&#xff0c;它們之間很可能出現沖突并導致在后續的工作中出現版本不匹配之類的錯誤。本文將討論筆者在使用 protobuf 中遇到的一些問題&#…

CMake常用命令整理

CMake常用命令整理 轉自&#xff1a;https://zhuanlan.zhihu.com/p/315768216 CMake 是什么我就不用再多說什么了&#xff0c;相信大家都有接觸才會看一篇文章。對于不太熟悉的開發人員可以把這篇文章當個查找手冊。 1.CMake語法 1.1 指定cmake的最小版本 cmake_minimum_r…

CVE-2021-41773 CVE-2021-42013 Apache HTTPd最新RCE漏洞復現 目錄穿越漏洞

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; CVE-2021-41773漏洞描述&#xff1a; Apache HTTPd是Apache基金會開源的一款流行的HTTP服務器。2021年10月8日Apache HTTPd官方發布安全更新&#xff0c;披…

SSRF,以weblogic為案例

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 復習一下ssrf的原理及危害&#xff0c;并且以weblog的ssrf漏洞為案例 漏洞原理 SSRF(Server-side Request Forge, 服務端請求偽造) 通常用于控制web進而…