精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

C++面向對象編程中的深拷貝和淺拷貝

jf_78858299 ? 來源:小余的自習室 ? 作者:小余的自習室 ? 2023-03-30 12:53 ? 次閱讀

前言

最近在寫代碼的過程中,發現一個大家容易忽略的知識點: 深拷貝和淺拷貝

可能對于Java程序員來說,很少遇到深淺拷貝問題,但是對于C++程序員來說可謂是又愛又恨。。

淺拷貝:

  • 1.將原對象或者原對象的引用直接賦值給新對象,新對象,新數組只是原對象的一個引用。

  • 2.C++默認的拷貝構造函數與賦值運算符重載都是淺拷貝,可以節省一定空間,但是可能會引發同一塊內存重復釋放問題,

    二次釋放內存可能導致嚴重的異常崩潰等情況。

  • 淺拷貝模型:

    圖片

深拷貝:

  • 1.創建一個新的對象或者數組,將對象或者數組的屬性值拷貝過來,注意此時新對象指向的不是原對象的引用而是原對象的值,新對象在堆中有自己的地址空間。

  • 2.浪費空間,但是不會引發淺拷貝中的資源重復釋放問題。

  • 深拷貝模型

    圖片

案例分析

下面使用一個案例來看下一個因為淺拷貝帶來的bug。

#include "DeepCopy.h"
#include 
#include 
using namespace std;
class Human {
public:
    Human(int age):_age(age) {


    }
    int _age;;

};
class String {
public:
    String(Human* pHuman){
        this->pHuman = pHuman;
    }
    ~String() {
        delete pHuman;
    }
    Human* pHuman;
};

void DeepCopy::main() 
{    
    Human* p = new Human(100);
    String s1(p);
    String s2(s1);

}

這個程序從表面看是不會有啥問題的,運行后,出現如下錯誤:

圖片

先說下原因

這個錯誤就是由于代碼 String s2(s1) 會調用String的默認拷貝構造函數,而 默認的拷貝構造函數使用的是淺拷貝,即僅僅只是對新的指針對象pHuman指向原指針對象pHuman指向的地址

在退出main函數作用域后,會回調s1和s2的析構函數,當回調s2析構函數后,s2中的pHuman內存資源被釋放,此時再回調s1,也會回調s1中的pHuman析構函數,可是此時的pHuman指向的地址

已經在s2中被釋放了,造成了二次釋放內存,出現了崩潰的情況

所以為了防止出現二次釋放內存的情況,需要使用深拷貝

深拷貝需要重寫拷貝構造函數以及賦值運算符重載,且在拷貝構造內部重新去new一個對象資源.

代碼如下:

#include "DeepCopy.h"
#include 
#include 
using namespace std;
class Human {
public:
    Human(int age):_age(age) {

}

int _age;;

};
class String {
public:
    String(Human* pHuman){
        this->pHuman = pHuman;
    }
    //重寫拷貝構造,實現深拷貝,防止二次釋放內存引發崩潰
    String(const String& str) {
        pHuman = new Human(str.pHuman->_age);
    }
    ~String() {
        delete pHuman;
    }
    Human* pHuman;
};

void DeepCopy::main() 
{    
    Human* p = new Human(100);
    String s1(p);
    String s2(s1);

}

默認情況下使用:

String s2(s1)或者String s2 = s1 這兩種方式去賦值,就會調用String的拷貝構造方法,如果沒有實現,則會執行默認的拷貝構造,即淺拷貝。

可以在拷貝構造函數中使用new重新對指針進行資源分配,達到深拷貝的要求、

說了這么多只要記住一點: 如果類中有成員變量是指針的情況下,就需要自己去實現深拷貝

雖然深拷貝可以幫助我們防止出現二次內存是否的問題,但是其會浪費一定空間,如果對象中資源較大,拿每個對象都包含一個大對象,這不是一個很好的設計,而淺拷貝就沒這個問題。

那么有什么方法可以兼容他們的優點么? 即不浪費空間也不會引起二次內存釋放

兼容優化方案:

  • 1.引用計數方式
  • 2.使用move語義轉移

引用計數

我們對資源增加一個引用計數,在構造函數以及拷貝構造函數中讓計數+1,在析構中讓計數-1.當計數為0時,才會去釋放資源,這是一個不錯的注意。

如圖所示:

圖片

對應代碼如下:

#include "DeepCopy.h"
#include 
#include 
using namespace std;
class Human {
public:
    Human(int age):_age(age) {

    }
    int _age;;
};
class String {
public:
    String() {
        addRefCount();
    }
    String(Human* pHuman){
        this->pHuman = pHuman;
        addRefCount();
    }
    //重寫拷貝構造,實現深拷貝,防止二次釋放內存引發崩潰
    String(const String& str) {
        ////深拷貝
        //pHuman = new Human(str.pHuman->_age);
        //淺拷貝
        pHuman = str.pHuman;
        addRefCount();
    }
    ~String() {
        subRefCount();
        if (getRefCount() <= 0) {
            delete pHuman;
        }   
    }
    Human* pHuman;
private:
    void addRefCount() {
        refCount++;
    }
    void subRefCount() {
        refCount--;
    }
    int getRefCount() {
        return refCount;
    }
    static int refCount;
};
int String::refCount = 0;
void DeepCopy::main() 
{    
    Human* p = new Human(100);
    String s1(p);
    String s2 = s1;

}

此時的拷貝構造函數使用了淺拷貝對成員對象進行賦值,且 只有在引用計數為0的情況下才會進行資源釋放

但是引用計數的方式會出現循環引用的情況,導致內存無法釋放,發生 內存泄露

循環引用模型如下:

圖片

我們知道在C++的 智能指針shared_ptr中就使用了引用計數

類似java中對象垃圾的定位方法,如果有一個指針引用某塊內存,則引用計數+1,釋放計數-1.如果引用計數為0,則說明這塊內存可以釋放了。

下面我們寫個shared_ptr循環引用的情況:

class A {
  public:
    shared_ptr pa;

**
~A() {

cout << "~A" << endl;

}

};
class B {
public:
    shared_ptr pb;


~B() {
cout << "~B" << endl;
}

};
void sharedPtr() {
    shared_ptr a(new A());
    shared_ptr b(new B());
    cout << "第一次引用:" << endl;
    cout <<"計數a:" << a.use_count() << endl;
    cout << "計數b:" << b.use_count() << endl;
    a->pa = b;
    b->pb = a;
    cout << "第二次引用:" << endl;
    cout << "計數a:" << a.use_count() << endl;
    cout << "計數b:" << b.use_count() << endl;
}
運行結果:
第一次引用:
計數a:1
計數b:1
第二次引用:
計數a:2
計數b:2

[**
可以看到運行結果并沒有打印出對應的析構函數,也就是沒被釋放。

指針a和指針b是棧上的,當退出他們的作用域后,引用計數會-1,但是其計數器數是2,所以還不為0,也就是不能被釋放。你不釋放我,我也不釋放你,咱兩耗著唄。

這就是標志性的由于循環引用計數導致的內存泄露.。所以 我們在設計深淺拷貝代碼的時候千萬別寫出循環引用的情況

move語義轉移

在C++11之前,如果要將源對象的狀態轉移到目標對象只能通過復制。

而現在在某些情況下,我們沒有必要復制對象,只需要移動它們。

C++11引入移動語義

源對象資源的控制權全部交給目標對象。注意這里說的是控制權,即使用一個新的指針對象去指向這個對象,然后將原對象的指針置為nullptr

模型如下:

圖片

要實現move語義,需要實現移動構造函數

代碼如下:

//移動語義move
class Human {
public:
    Human(int age) :_age(age) {

}

int _age;;

};
class String {
public:

String(Human* pHuman) {

this->pHuman = pHuman;

}

//重寫拷貝構造,實現深拷貝,防止二次釋放內存引發崩潰

String(const String& str) {

////深拷貝

//pHuman = new Human(str.pHuman->_age);

//淺拷貝

pHuman = str.pHuman;

}

//移動構造函數

String(String&& str) {

pHuman = str.pHuman;

str.pHuman = NULL;

}

~String() {

if (pHuman != NULL) {

delete pHuman;

}

}

Human* pHuman;

};
void DeepCopy::main()
{
    Human* p = new Human(100);
    String s1(p);

String s2(std::move(s1));

String s3(std::move(s2));

}

該案例中, 指針p的權限會由s1讓渡給s2,s2再讓渡給s3 .

使用move語義轉移在C++中使用還是比較頻繁的,因為其可以大大縮小因為對象對象的創建導致內存吃緊的情況。比較推薦應用中使用這種方式來優化內存方面問題.

總結

本篇文章主要講解了C++面向對象編程中的深拷貝和淺拷貝的問題,以及使用引用計數和move語義轉移的方式來優化深淺拷貝的問題。

C++不像Java那樣,JVM都給我們處理好了資源釋放的問題,沒有二次釋放導致的崩潰情況, C++要懂的東西遠非Java可比,這也是為什么C++程序員那么少的原因之一吧

]()

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2960

    瀏覽量

    104563
  • C++
    C++
    +關注

    關注

    22

    文章

    2104

    瀏覽量

    73503
  • 面向對象編程

    關注

    0

    文章

    22

    瀏覽量

    1807
收藏 人收藏

    評論

    相關推薦

    基于C/C++面向對象的方式封裝socket通信類

    在掌握了基于 TCP 的套接字通信流程之后,為了方便使用,提高編碼效率,可以對通信操作進行封裝,本著有的原則,先基于 C 語言進行面向過程的函數封裝,然后再基于
    的頭像 發表于 12-26 09:57 ?1286次閱讀

    C++零基礎教程之C++拷貝拷貝,輕松上手C++拷貝拷貝

    編程語言C++語言
    電子學習
    發布于 :2023年01月14日 11:37:32

    拷貝拷貝的實現方法概述

    拷貝拷貝的實現
    發表于 07-19 13:35

    python深淺拷貝是什么?

    python的直接賦值python的拷貝python的拷貝
    發表于 11-04 08:33

    請問哪位大神可以詳細介紹JavaScript拷貝拷貝

    JavaScript數據類型JavaScript拷貝拷貝
    發表于 11-05 07:16

    C++ 面向對象多線程編程下載

    C++ 面向對象多線程編程下載
    發表于 04-08 02:14 ?70次下載

    面向對象的程序設計(C++

    面向對象的程序設計(C++).面向對象的基本思想 C++
    發表于 03-22 14:40 ?0次下載

    C#拷貝拷貝區別解析

     所謂拷貝就是將對象的所有字段復制到新的副本對象
    發表于 11-29 08:32 ?2.6w次閱讀
    <b class='flag-5'>C</b>#<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>與<b class='flag-5'>深</b><b class='flag-5'>拷貝</b>區別解析

    Python如何防止數據被修改Python拷貝拷貝的問題說明

    在平時工作,經常涉及到數據的傳遞。在數據傳遞使用過程,可能會發生數據被修改的問題。為了防止數據被修改,就需要再傳遞一個副本,即使副本被修改,也不會影響原數據的使用。為了生成這個副本,就產生了拷貝——今天就說一下Python
    的頭像 發表于 03-30 09:54 ?3036次閱讀
    Python如何防止數據被修改Python<b class='flag-5'>中</b>的<b class='flag-5'>深</b><b class='flag-5'>拷貝</b>與<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>的問題說明

    C++:詳談拷貝構造函數

    只有單個形參,而且該形參是對本類類型對象的引用(常用const修飾),這樣的構造函數稱為拷貝構造函數。拷貝構造函數是特殊的構造函數,創建對象時使用已存在的同類
    的頭像 發表于 06-29 11:45 ?2112次閱讀
    <b class='flag-5'>C++</b>:詳談<b class='flag-5'>拷貝</b>構造函數

    C++拷貝構造函數的copy及copy

    C++編譯器會默認提供構造函數;無參構造函數用于定義對象的默認初始化狀態;拷貝構造函數在創建對象拷貝
    的頭像 發表于 12-24 15:31 ?718次閱讀

    C語言是怎么面向對象編程

    在嵌入式開發C/C++語言是使用最普及的,在C++11版本之前,它們的語法是比較相似的,只不過C++提供了
    的頭像 發表于 02-14 13:57 ?1638次閱讀
    <b class='flag-5'>C</b>語言是怎么<b class='flag-5'>面向</b><b class='flag-5'>對象</b><b class='flag-5'>編程</b>

    C/C++面向對象編程思想3

    C++作為一門在C和Java之間的語言,其既可以使用C語言中的高效指針,又繼承了Java面向對象
    的頭像 發表于 03-30 15:16 ?540次閱讀
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>之<b class='flag-5'>面向</b><b class='flag-5'>對象</b><b class='flag-5'>編程</b>思想3

    C++拷貝拷貝詳解

    當類的函數成員存在指針成員時會產生拷貝拷貝和問題。
    發表于 08-21 15:05 ?320次閱讀
    <b class='flag-5'>C++</b><b class='flag-5'>深</b><b class='flag-5'>拷貝</b>和<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>詳解

    Python拷貝拷貝的操作

    【例子】拷貝拷貝 list1 = [ 123 , 456 , 789 , 213 ]list2 = list1list3 = lis
    的頭像 發表于 11-02 10:58 ?379次閱讀