欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

c++動(dòng)態(tài)內(nèi)存管理與智能指針的相關(guān)知識(shí)點(diǎn)

 更新時(shí)間:2022年03月01日 16:04:48   作者:萌之上  
為了更容易同時(shí)也更安全地使用動(dòng)態(tài)內(nèi)存,新的標(biāo)準(zhǔn)庫提供了兩種智能指針(smart pointer)類型來管理對(duì)象,下面這篇文章主要給大家介紹了關(guān)于c++動(dòng)態(tài)內(nèi)存管理與智能指針的相關(guān)知識(shí)點(diǎn),需要的朋友可以參考下

引言

程序使用三種不同的內(nèi)存

靜態(tài)內(nèi)存:static成員以及任何定義在函數(shù)之外的變量棧內(nèi)存:一般局部變量堆內(nèi)存(自由空間):動(dòng)態(tài)分配的對(duì)象

靜態(tài)內(nèi)存和棧內(nèi)存中的變量由編譯器產(chǎn)生和銷毀,動(dòng)態(tài)分配的對(duì)象在我們不再使用它時(shí)要由程序員顯式地銷毀

一、介紹

動(dòng)態(tài)分配內(nèi)存

  • new():為對(duì)象分配空間,并返回指向該對(duì)象的指針
  • delete:銷毀對(duì)象,并釋放與之相關(guān)的內(nèi)存

使用智能指針:定義在頭文件memory中

  • shared_ptr:允許多個(gè)指針指向同一個(gè)對(duì)象
  • unique_ptr:“獨(dú)占”所使用的對(duì)象
  • weak_ptr:伴隨類,弱引用,指向shared_ptr所管理的對(duì)象

和容器一樣,只能指針也是一種模板,需要給它傳入一個(gè)參數(shù)來指定類型

二、shared_ptr類

聲明shared_ptr:

shared_ptr<string> p1;	   //shared_ptr,可以指向string
shared_ptr<list<int>> p2;  //shared_ptr,可以指向list<int>

使用方式與普通指針一致,解引用返回它所指向的對(duì)象,在條件表達(dá)式中檢查是否為空

//若p1不為空且指向一個(gè)空string
if(p1 && p1->empty()){
    *p1 = "hi";   //對(duì)p1重新賦值
}

make_shared函數(shù)

make_shared<typename>(arguments)

在動(dòng)態(tài)內(nèi)存中分配并初始化一個(gè)對(duì)象

返回指向此對(duì)象的shared_ptr指針

//指向一個(gè)值為42的int的shared_ptr
shared_ptr<int> p1 = make_shared<int>(42);
//指向一個(gè)值為"999"的string的shared_ptr
shared_ptr<string> p2 = make_shared<string>(3, '9');
//指向一個(gè)值為0的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>();

沒有傳入?yún)?shù)時(shí),進(jìn)行值初始化

auto p4 = make_shared<string>(); //p4指向空string

shared_ptr的拷貝和引用

每個(gè)share_ptr都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器

當(dāng)拷貝shared_ptr時(shí),計(jì)數(shù)器會(huì)遞增

當(dāng)shared_ptr被賦予新值或者shared_ptr被銷毀(如一個(gè)局部的shared_ptr離開其作用域),計(jì)數(shù)器會(huì)遞減

當(dāng)一個(gè)shared_ptr的計(jì)數(shù)器==0時(shí),內(nèi)存會(huì)被釋放

auto r = make_shared<int>(42);
r = q; //給r賦值,使它指向另一個(gè)地址
	   //遞增q指向的對(duì)象的引用計(jì)數(shù)
	   //遞減r指向的對(duì)象的引用計(jì)數(shù)
       //如果計(jì)數(shù)器為0,自動(dòng)釋放

shared_ptr自動(dòng)銷毀所管理的對(duì)象…

和其他類一樣,shared_ptr類型也有析構(gòu)函數(shù)

shared_ptr的析構(gòu)函數(shù)會(huì)

  1. 遞減指針?biāo)赶虻膶?duì)象的引用計(jì)數(shù)
  2. 當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),銷毀對(duì)象并釋放內(nèi)存…shared_ptr還會(huì)自動(dòng)釋放相關(guān)聯(lián)對(duì)象的內(nèi)存

舉例:

//factory返回一個(gè)share_ptr,指向一個(gè)動(dòng)態(tài)分配的對(duì)象
shared_ptr<Foo> factory(T arg){
    //對(duì)arg的操作
    return make_shared<Foo>(arg);
}

void ues_factory(T arg){
    shared_ptr<Foo> p = factory(arg);
    //使用p
}
//p離開了作用域,由于引用計(jì)數(shù)由1減到0,對(duì)象被銷毀,內(nèi)存釋放

如果有其他引用計(jì)數(shù)也指向該對(duì)象,則對(duì)象內(nèi)存不會(huì)被釋放掉

//factory和上述一致
//ues_factory返回shared_ptr的拷貝
void use_factory(T arg){
    shared_ptr<Foo> p = factory(arg);
    //使用p
    return p; //返回p的拷貝,此時(shí)遞增了計(jì)數(shù)器,引用數(shù)為2
}//p離開作用域,對(duì)象計(jì)數(shù)器引用2-1=1,對(duì)象內(nèi)存沒有釋放

return shared_ptr時(shí),如果不是返回引用類型,則會(huì)進(jìn)行拷貝,shared_ptr的計(jì)數(shù)器+1后-1,最終shared的計(jì)數(shù)器不變

由于在最后一個(gè)shared _ptr銷毀前內(nèi)存都不會(huì)釋放,保證shared_ptr在無用之后不再保留就非常重要了。如果你忘記了銷毀程序不再需要的shared_ptr,程序仍會(huì)正確執(zhí)行,但會(huì)浪費(fèi)內(nèi)存。

share_ptr 在無用之后仍然保留的一種可能情況是,你將shared _ptr存放在一個(gè)容器中,隨后重排了容器,從而不再需要某些元素。在這種情況下,你應(yīng)該確保用erase刪除那些不再需要的shared_ptr元素。

如果你將shared ptr存放于一個(gè)容器中,而后不再需要全部元素,而只使用其中一部分,要記得用erase刪除不再需要的那些元素。

使用動(dòng)態(tài)生存期的資源的類

程序使用動(dòng)態(tài)內(nèi)存的三種原因

  • 程序不知道自己需要使用多少對(duì)象
  • 不知道所需對(duì)象的準(zhǔn)確類型
  • 需要在多個(gè)對(duì)象間共享數(shù)據(jù)

容器類常出于第一種原因使用動(dòng)態(tài)內(nèi)存,在15章會(huì)看見出于第二種原因的例子,本節(jié)討論第三種原因

先考慮這么一種情況:

我們要定義一個(gè)Blob類,當(dāng)該類型的對(duì)象拷貝時(shí),對(duì)象共享底層數(shù)據(jù)。

如b2 = b1時(shí),b2,b1共享底層數(shù)據(jù),對(duì)b2的操作也會(huì)印象到b1,且銷毀b2時(shí),b1的仍指向原數(shù)據(jù)

Blob<string> b1; //空Blob
{
    //新作用域
    Blob<string> b2 = {"a","an","the"};
    b1 = b2; //b1和b2共享數(shù)據(jù)
}//b2離開作用域,被銷毀了,但b2的數(shù)據(jù)不能被銷毀
//b1指向b2的原數(shù)據(jù)

應(yīng)用舉例:Blob類

定義Blob類

最終,我們希望將Blob定義為一個(gè)模板類,但現(xiàn)在我們先將其定義為StrBlob,即底層數(shù)據(jù)是vector<string>的Blob

class StrBlob{
public:
    //拷貝控制
    StrBlob();//默認(rèn)構(gòu)造函數(shù)
    StrBlob(initializer_list<string> il); //列表初始化
    StrBlob(const StrBlob& strb);
    //查詢
    int size() const {return data->size();}
    bool empty() const {return data->empty();}
    //添加和刪除元素
    void push_back(const string &t) {data->push_back(t);}
    void pop_back() {data->pop_back();}
    //訪問元素
    string& front();
    string& back();
private:
    shared_ptr<vector<string>> data;
    //如果data[i]不合法,拋出異常
    void check(int i, const string &msg) const;
};

StrBlob的構(gòu)造函數(shù)

StrBlob::StrBlob() : data(make_shared<vector<string>>())
        {cout<<"in StrBlob dafault"<<endl;};
StrBlob::StrBlob(initializer_list<string> il) :
    data(make_shared<vector<string>>(il))
    {cout<<"in StrBlob initializer_list"<<endl;}

元素訪問成員函數(shù)

在訪問時(shí)必須保證容器非空,定義check函數(shù)進(jìn)行檢查

void StrBlob::check(int i, const string& msg) const{
    if(i >= data->size())
        throw out_of_range(msg);
}

元素訪問成員函數(shù):

string& StrBlob::front(){
    //如果vector為空,check會(huì)拋出一個(gè)異常
    check(0, "front on empty StrBlob");
    return data->front();
}
string& StrBlob::back(){
    check(0, "back on empty StrBlob");
    return data->back();
}

StrBlob的拷貝、賦值和銷毀

StrBlob使用默認(rèn)的拷貝、賦值和析構(gòu)函數(shù)對(duì)此類型的對(duì)象進(jìn)行操作

當(dāng)我們對(duì)StrBlob對(duì)象進(jìn)行拷貝、賦值和銷毀時(shí),它的shared_ptr成員也會(huì)默認(rèn)地進(jìn)行拷貝、賦值和銷毀

//由于data是private的
//在StrBlob中設(shè)置一個(gè)接口look_data
//look_data返回data的引用
class StrBlob{
public:
    //...
    shared_ptr<vector<string>>& look_data()
    {return data;} //返回引用,避免對(duì)象拷貝
private:
    //其余部分都不變
};

測試程序:

//測試程序
int main(){
    StrBlob b1;
    {//新作用域
        StrBlob b2 = {"first element","second element"};
        cout<<"before assignment : "
            <<b2.look_data().use_count()<<endl;
        b1 = b2;
        cout<<"after assignment  : "
            <<b2.look_data().use_count()<<endl;
    }//b2被銷毀,計(jì)數(shù)器遞減
    //b1仍指向b2的原數(shù)據(jù)
    cout<<b1.front()<<endl;
    //打印此時(shí)b1的計(jì)數(shù)器
    cout<<"b2 has been dstoryed : "
        <<b1.look_data().use_count()<<endl;
    return 0;
}

輸出結(jié)果:

如果look_data用值返回,而不是引用返回,那么會(huì)存在拷貝【見6.2.2節(jié)筆記】,所有計(jì)數(shù)器的值會(huì)+1

三、直接管理內(nèi)存

使用new分配內(nèi)存

  • new分配動(dòng)態(tài)內(nèi)存
  • delete銷毀動(dòng)態(tài)內(nèi)存

new和delete與智能指針不同,類對(duì)象的拷貝、賦值和銷毀操作都不會(huì)默認(rèn)地對(duì)動(dòng)態(tài)分配的對(duì)象進(jìn)行管理,無論是對(duì)象的創(chuàng)建還是銷毀,都需要程序員顯式地操作,在大型的應(yīng)用場景中會(huì)十分復(fù)雜。

在熟悉C++拷貝控制之前,盡量只使用智能指針,而不是本節(jié)的方法管理動(dòng)態(tài)內(nèi)存

使用new動(dòng)態(tài)分配和初始化對(duì)象

new type_name:返回一個(gè)指向該對(duì)象的指針

//pi指向一個(gè)動(dòng)態(tài)分配,默認(rèn)初始化的無名對(duì)象
int *pi = new int;
//*pi的值是未定義的
cout<<*pi<<endl;

對(duì)象是默認(rèn)初始化這意味著:

  1. 指向的是:內(nèi)置類型和組合類型對(duì)象。對(duì)象的值是未定義的
  2. 指向的是:類類型對(duì)象。調(diào)用默認(rèn)構(gòu)造函數(shù)

可以直接初始化動(dòng)態(tài)分配的對(duì)象

  • 直接調(diào)用構(gòu)造函數(shù)
  • 列表初始化
//pi指向?qū)ο蟮闹禐?2
int *pi = new int(42); 
//"9999999999"
string *ps = new string(10, '9'); 
//vector有5個(gè)元素,依次為0,1,2,3,4
vector<int> *pv = new vector<int>{0,1,2,3,4};

也可以值初始化

string *ps1 = new string(); //值初始化為空string
string *ps = new string;    //默認(rèn)初始化為空string
int *pi1 = new int;         //默認(rèn)初始化,值未定義
int *pi  = new int();       //值初始化,*pi = 0;

所以,初始化動(dòng)態(tài)分配的對(duì)象是一個(gè)好習(xí)慣

動(dòng)態(tài)分配const對(duì)象

用new可以分配const對(duì)象

和其他const對(duì)象一樣,動(dòng)態(tài)分配的const對(duì)象必須被初始化

//分配并初始化const int
const int *pi = new const int(1024);
//分配并默認(rèn)初始化const string
const string *ps = new const string;

內(nèi)存耗盡

如果new分配動(dòng)態(tài)內(nèi)存失敗,返回一個(gè)空指針,并報(bào)出std::bad_alloc異常

int *p1 = new int; //返回空指針,拋出異常
int *p2 = new (nothrow) int; //如果分配失敗,new返回空指針

我們第二種形式的new為定位new (placement new),其原因我們將在19.1.2節(jié)(第729頁)中解釋。

定位new表達(dá)式允許我們向new傳遞額外的參數(shù)。

在此例中,我們傳遞給它一個(gè)由標(biāo)準(zhǔn)庫定義的名為nothrow的對(duì)象。如果將nothrow傳遞給new,我們的意圖是告訴它不能拋出異常。如果這種形式的 new不能分配所需內(nèi)存,它會(huì)返回一個(gè)空指針。bad_alloc和nothrow都定義在頭文件new中。

使用delete釋放內(nèi)存

基本介紹

delete():接受一個(gè)指針,指向我們想要銷毀的對(duì)象

執(zhí)行兩個(gè)操作

  • 銷毀對(duì)象
  • 釋放對(duì)應(yīng)的內(nèi)存

注意點(diǎn):

  1. 保證只傳給delete動(dòng)態(tài)分配的指針,將一般指針傳給delete,其行為是未定義的
  2. 同一塊內(nèi)存不能釋放兩次
  3. 不要忘記delete內(nèi)存
  4. 不要使用已經(jīng)delete的對(duì)象
int i, *pi = &i;
int *pd = new int();
delete pd; //正確:釋放pd內(nèi)存
pd = nullptr; //好習(xí)慣:指出pd不再指向動(dòng)態(tài)內(nèi)存
delete pi; //未定義:pi沒有指向動(dòng)態(tài)分配的內(nèi)存
delete pd; //未定義:pd內(nèi)存已經(jīng)被釋放

保證以上兩點(diǎn)是程序員的責(zé)任,編譯器并不會(huì)檢查以上錯(cuò)誤

舉例

在被顯式地delete前,用new動(dòng)態(tài)分配的內(nèi)存一直存在

Foo* factory(T arg){
    //處理arg
    return new Foo(arg);
}//調(diào)用者負(fù)責(zé)釋放

void ues_factory(T arg){
    Foo *p = factory(arg);
    //使用p但不delete它
}//p離開了作用域,但它所指向的內(nèi)存沒有被釋放?。?/pre>

use_factory返回時(shí),局部變量p被銷毀。但此變量是一個(gè)內(nèi)置指針,而不是一個(gè)智能指針,所以p所指向的內(nèi)存并沒有被銷毀。

這樣就產(chǎn)生了一塊無名的內(nèi)存塊,存在又無法刪除。

這也體現(xiàn)了智能指針與普通指針的區(qū)別:智能指針在離開自己的作用域,自己的變量名失效時(shí),銷毀指向的對(duì)象并釋放關(guān)聯(lián)內(nèi)存;而new產(chǎn)生的指針不會(huì)。

修改use_factory:

void use_factory(T arg){
    Foo *p = factory(arg);
    //使用p
    delete p;  //記得釋放p
}

堅(jiān)持使用智能指針,可以避免上述的絕大部分問題

四、shared_ptr和new結(jié)合使用

new直接初始化share_ptr

可以用new返回的指針初始化share_ptr

該構(gòu)造函數(shù)是explicit的

所以,不存在new產(chǎn)生的指針向shared_ptr的隱式類型轉(zhuǎn)換,必須采用直接初始化,而不是拷貝初始化或者賦值

shared_ptr<int> p1(new int(42)); //正確:使用直接初始化
shared_ptr<int> p2 = new int(30);//錯(cuò)誤:new產(chǎn)生的指針

同理,返回shared_ptr的函數(shù)不能返回new產(chǎn)生的指針

shared_ptr<int> clone(int p){
    //錯(cuò)誤:構(gòu)造函數(shù)為explicit,無法轉(zhuǎn)換
    //return new int(p);  
    //正確:顯式地用int*構(gòu)造shared_ptr<int>
    return shared_ptr<int>(new int(p));
}

如對(duì)隱式類型轉(zhuǎn)換有疑問查看 7-5筆記第三點(diǎn)”隱式類類型轉(zhuǎn)換”

初始化時(shí)傳入可調(diào)用對(duì)象代替delete

默認(rèn)情況下,一個(gè)用來初始化智能指針的普通指針必須指向動(dòng)態(tài)內(nèi)存,因?yàn)橹悄苤羔樐J(rèn)使用delete釋放它所關(guān)聯(lián)的對(duì)象。我們可以將智能指針綁定到一個(gè)指向其他類型的資源的指針上,但是為了這樣做,必須提供自己的操作來替代 delete。我們將在12.1.4節(jié)介紹如何定義自己的釋放操作。

五、unique_ptr

和shared_ptr不同,某個(gè)時(shí)刻只能有一個(gè)unique_ptr指向一個(gè)給定對(duì)象

基本操作

必須采用直接初始化

unique_ptr<double> p1;  //可以指向double的一個(gè)unique_ptr
unique_ptr<int> p2(new int(42)); //p2指向一個(gè)值為42的int

unique_ptr不支持拷貝與賦值

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1); //錯(cuò)誤:不支持拷貝
unique_ptr<string> p3;
p3 = p1; //錯(cuò)誤:不支持賦值

unique_ptr支持的操作

可以使用release和reset將指針的所有權(quán)從一個(gè)(非const)unique_ptr轉(zhuǎn)移到另一個(gè)unique_ptr

//將所有權(quán)從p1,轉(zhuǎn)移到p2
unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1.release()); //release將p1置空
cout<<*p2<<endl; //輸出 hello
unique_ptr<string> p3(new string("world"));
//p2綁定的對(duì)象被釋放,p3置空,p2指向p3原來指向的對(duì)象
p2.reset(p3.release());
cout<<*p2<<endl; //輸出: world

傳遞和返回unique_ptr

不能拷貝unique_ptr 的規(guī)則有一個(gè)例外:我們可以拷貝或賦值一個(gè)將要被銷毀的unique_ptr。最常見的例子是從函數(shù)返回一個(gè)unique_ptr:

unique_ptr<int> clone(int p){
    //正確:從int*創(chuàng)建一個(gè)unique_ptr<int>
    return unique_ptr<int>(new int(p));
}

還可以返回一個(gè)局部變量的拷貝

unique_ptr<int> clone(int p){
    unique_ptr<int> ret(new int(p));
    return ret;
}

對(duì)于兩段代碼,編譯器都知道要返回的對(duì)象將要被銷毀。在此情況下,編譯器執(zhí)行一種特殊的“拷貝”,我們將在13.6.2節(jié)(移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符)中介紹它。

向unique_ptr傳遞刪除器

//p指向一個(gè)類型為objT的對(duì)象
//并使用一個(gè)類型為delT的可調(diào)用對(duì)象釋放objT
//p會(huì)使用一個(gè)名為fcnd的delT對(duì)象來刪除objT
unique_ptr<objT, delT> p(new objT, fcn);

作為一個(gè)更具體的例子,我們將寫一個(gè)連接程序,用unique_ptr來代替shared_ptr,如下所示:

void f(destination &d /*其他需要的參數(shù)*/)
{
	connection c = connect(&d);//打開鏈接
	unique_ptr<connection, decltype(end_connection)*>
		p(&c, end_connection);
	//使用鏈接
	//當(dāng)f退出時(shí)(即使是由于異常而退出)
	//connection會(huì)調(diào)用end_connection正常退出
}

注意decltype(end_connection)返回一個(gè)函數(shù)類型,而函數(shù)類型不能作為參數(shù),函數(shù)指針可以

所以要加上*表示函數(shù)指針
p(&c, end_connection)中,類似于數(shù)組名表示指針一樣,函數(shù)名實(shí)際上就表示函數(shù)指針

所以也可寫作p(&c, &end_connection),但沒必要?!厩耙粋€(gè)&表示引用傳遞,后一個(gè)&表示取址得到指針】

總結(jié)

到此這篇關(guān)于c++動(dòng)態(tài)內(nèi)存管理與智能指針的文章就介紹到這了,更多相關(guān)c++動(dòng)態(tài)內(nèi)存管理與智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論