12.1 動(dòng)態(tài)內(nèi)存和智能指針 | dynamic memory & smart pointer

12章之前的程序中使用的對(duì)象都有嚴(yán)格定義的生存期。

  • 全局對(duì)象在程序啟動(dòng)時(shí)分配,在程序結(jié)束時(shí)銷毀。
  • 對(duì)于局部自動(dòng)對(duì)象,當(dāng)進(jìn)入其定義所在的程序塊時(shí)被創(chuàng)建,在離開塊時(shí)銷毀。
  • 局部static對(duì)象在第一次使用前分配,在程序結(jié)束時(shí)銷毀。

除了自動(dòng)和static對(duì)象,還支持動(dòng)態(tài)分配對(duì)象。這些對(duì)象的生存期與創(chuàng)建位置無關(guān),顯式被釋放時(shí)才會(huì)被銷毀。

為了安全使用動(dòng)態(tài)對(duì)象,標(biāo)準(zhǔn)庫中有兩個(gè)智能指針類型管理動(dòng)態(tài)分配的對(duì)象。當(dāng)一個(gè)對(duì)象應(yīng)該被釋放時(shí),指向它的智能指針可以確保自動(dòng)地釋放它。

靜態(tài)內(nèi)存和棧內(nèi)存

  • 靜態(tài)內(nèi)存用來保存局部static對(duì)象、類static數(shù)據(jù)成員以及定義在任何函數(shù)之外的變量。
  • 棧內(nèi)存用來保存定義在函數(shù)內(nèi)的非static對(duì)象。
  • 分配在靜態(tài)或棧內(nèi)存中的對(duì)象由編譯器自動(dòng)創(chuàng)建和銷毀。棧對(duì)象僅在其定義的程序塊運(yùn)行時(shí)存在;static對(duì)象在使用前分配,程序結(jié)束時(shí)銷毀。

除了靜態(tài)內(nèi)存和棧內(nèi)存,每個(gè)程序還擁有一個(gè)內(nèi)存池。這些內(nèi)存稱作自由空間(free store)堆(heap)。

程序用堆來存儲(chǔ)動(dòng)態(tài)分配(dynamically allocated)對(duì)象,即在程序運(yùn)行時(shí)分配的對(duì)象。動(dòng)態(tài)對(duì)象的生存期由程序控制,因此在不需要時(shí)需要顯式銷毀。

動(dòng)態(tài)內(nèi)存和智能指針

C++中的動(dòng)態(tài)內(nèi)存管理是通過一堆運(yùn)算符完成的:

  • new在動(dòng)態(tài)內(nèi)存中為對(duì)象分配空間并返回一個(gè)指向該對(duì)象的指針;
  • delete接受一個(gè)動(dòng)態(tài)對(duì)象的指針,銷毀該對(duì)象并釋放關(guān)聯(lián)的內(nèi)存。

標(biāo)準(zhǔn)庫中提供了兩種智能指針(smart pointer)類型管理動(dòng)態(tài)對(duì)象。智能指針的行為類似常規(guī)指針,重要的區(qū)別是它負(fù)責(zé)自動(dòng)釋放所指向的對(duì)象。新標(biāo)準(zhǔn)庫提供的這兩種智能指針的區(qū)別在于管理底層指針的方式:

  • shared_ptr允許多個(gè)指針指向同一個(gè)對(duì)象
  • unique_ptr“獨(dú)占”指向的對(duì)象

標(biāo)準(zhǔn)庫還有一個(gè)名為weak_ptr的伴隨類,是一種弱引用,指向shared_ptr所管理的對(duì)象。

上述三種類型都定義在<memory>頭文件中。

shared_ptr

類似vector,智能指針也是模板,因此創(chuàng)建一個(gè)智能指針時(shí),必須給出指向的類型:

std::shared_ptr<int> p1;
std::shared_ptr<std::vector<int>> p2;

默認(rèn)初始化的智能指針中保存著一個(gè)空指針。智能指針的使用方式類似普通指針,可以解引用返回對(duì)象。

shared_ptr和unique_ptr都支持的操作

expression -
shared_ptr<T> sp | unique_ptr<T> up 空智能指針
p 將p用作一個(gè)條件判斷,若指向一個(gè)對(duì)象則true
*p 解引用
p->mem *p.mem
p.get() 返回p中保存的指針。注意是否已經(jīng)釋放了對(duì)象
swap(p, q)|p.swap(q)

shared_ptr獨(dú)有操作

expression -
make_shared<T>(args) 返回一個(gè)shared_ptr,指向一個(gè)動(dòng)態(tài)分配的類型為T的對(duì)象,使用args初始化該對(duì)象
shared_ptr<T>p(q) p是shared_ptr q的拷貝。該操作會(huì)遞增q中的計(jì)數(shù)器,q中的指針必須可以轉(zhuǎn)換為T*
p=q 二者都是shared_ptr且保存的指針必須可以相互轉(zhuǎn)換。該操作會(huì)遞減p的引用計(jì)數(shù),遞增q的引用計(jì)數(shù)。若p的引用計(jì)數(shù)變?yōu)?,則將其管理的原內(nèi)存釋放。
p.unique() p.use_count()為1(獨(dú)占狀態(tài))則true,否則false
p.use_count() 返回與p共享對(duì)象的智能指針數(shù)量;主要用于調(diào)試

make_shared函數(shù)

make_shared是最安全的分配和使用動(dòng)態(tài)內(nèi)存的方法,避免了在定義后才初始化可能造成的錯(cuò)誤。該函數(shù)同樣定義在<memory>中:

int main(){
    std::shared_ptr<int> p1 = std::make_shared<int>(42);
    std::shared_ptr<std::vector<int>> p2 = std::make_shared<std::vector<int>>(10, 0);
    auto p3 = std::make_shared<std::map<std::string, int>>();
}

使用make_shared構(gòu)造智能指針時(shí)可以使用auto方式。
若不傳遞任何參數(shù),則會(huì)使用值初始化。
類似順序容器的emplace,make_shared用其參數(shù)構(gòu)造給定類型對(duì)象時(shí)傳遞的參數(shù)必須與string的某個(gè)構(gòu)造函數(shù)相匹配。
可以使用make_shared和初始化列表

auto sp = std::make_shared<std::vector<int>>(std::initializer_list<int>({1,2,32}));
//或者
auto sp_map = std::make_shared<std::map<std::string, int>>();
*sp_map = {{"A", 0},{"B", 1}};

shared_ptr的拷貝和賦值

進(jìn)行拷貝和賦值操作,每個(gè)shared_ptr都會(huì)記錄有多少個(gè)其他shared_ptr指向相同對(duì)象:

auto p = make_shared<int>(42);
auto q(p);

每個(gè)shared_ptr都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器,通常稱為引用計(jì)數(shù)(reference count)。無論何時(shí)拷貝一個(gè)shared_ptr,計(jì)數(shù)器都會(huì)遞增。

  • 將一個(gè)shared_ptr初始化另一個(gè)shared_ptr,或?qū)⑵渥鳛閰?shù)傳給另一個(gè)函數(shù)以及作為函數(shù)的返回值,則它所關(guān)聯(lián)的計(jì)數(shù)器就會(huì)遞增。
  • 當(dāng)給shared_ptr一個(gè)新值或者被銷毀(如局部shared_ptr)離開作用域,計(jì)數(shù)器會(huì)遞減
  • 一旦一個(gè)shardd_ptr的計(jì)數(shù)器變?yōu)?,則會(huì)自動(dòng)釋放自己管理的對(duì)象。
int main(){
    int a = 0;
    int b = 1;

    auto sp3 = std::make_shared<int>(a);
    auto sp4 = std::make_shared<int>(b);
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    sp3 = sp4;  //指向同一對(duì)象(a),使得該對(duì)象引用計(jì)數(shù)+1
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    *sp4 = 1;   //改變對(duì)象a的值,引用計(jì)數(shù)不變
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    sp3 = std::make_shared<int>(b);    //令sp3指向其他對(duì)象,原對(duì)象a的引用計(jì)數(shù)-1
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
}

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

當(dāng)指向一個(gè)對(duì)象的最后一個(gè)shared_ptr被銷毀(所指向?qū)ο蟮囊糜?jì)數(shù)歸0),shared_ptr類會(huì)自動(dòng)調(diào)用該對(duì)象類的析構(gòu)函數(shù)(destructor)銷毀該對(duì)象

shared_ptr自動(dòng)釋放相關(guān)聯(lián)的內(nèi)存

動(dòng)態(tài)對(duì)象不再使用時(shí),shared_ptr會(huì)自動(dòng)釋放動(dòng)態(tài)對(duì)象。

例如有一個(gè)函數(shù)返回shared_ptr,指向一個(gè)Foo類型的動(dòng)態(tài)分配的對(duì)象:

struct Foo{
public:
    std::string s;
    int i;
    Foo(std::string str, int ii);
};

Foo::Foo(std::string str, int ii) {
    this->i = ii;
    this->s = str;
}

std::shared_ptr<Foo> foo(std::string s, int i){
    return std::make_shared<Foo>(s, i);
}

auto try_ptr(std::string s, int i, bool return_ptr = 0){
    auto p = foo(s, i);
    //離開該作用域,p指向的對(duì)象被自動(dòng)釋放
    //若存在返回,則指向?qū)ο蟮囊糜?jì)數(shù)+1,因此不會(huì)被釋放
    return return_ptr? p: nullptr;
}

int main(){
    std::string str = "A";
    int ii = 1;
    auto ptr = foo(str, ii);
    std::cout<<ptr->i<<'\t'<<ptr->s<<std::endl;
}

除此之外,shared_ptr會(huì)在無用之后依然保留的情況是,將shared_ptr放在一個(gè)容器中,之后重排了容器,從而不需要某些元素。這種情況下應(yīng)該使用erase刪除那些不必要的shared_ptr元素。

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

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

  1. 不知道自己需要多少對(duì)象
  2. 不知道對(duì)象的準(zhǔn)確類型
  3. 需要在多個(gè)對(duì)象間共享數(shù)據(jù)

對(duì)于容器類,是由于第二種原因而使用的。

對(duì)于vector,拷貝一個(gè)vector時(shí)原有的vector和副本vector中的元素時(shí)相互分離的:

std::vector<int> v1;
{
    std::vector<int> v2 = {0, 1, 2};
    v1 = v2;
}
//此時(shí)離開作用域,v2及其中的元素被銷毀但是v1拷貝的元素存在

假定類Blob會(huì)在不同對(duì)象的拷貝間共享相同的元素,則離開作用域時(shí)

Blob<int> b1
{
    Blob<int> b2 = {0, 1, 2};
    b1 = b2;
}
//離開作用域時(shí)需保證b2元素不能銷毀

定義StrBlob類、構(gòu)造函數(shù)、成員函數(shù)

class StrBlob{
public:
    typedef std::vector<std::string>::size_type size_type;
    //默認(rèn)構(gòu)造函數(shù)
    StrBlob();
    //以接受初始化列表的構(gòu)造函數(shù)
    StrBlob(std::initializer_list<std::string> il);
    //基本方法
    size_type size() const {return data->size();};
    bool empty() const {return data->empty();};
    void push_back(const std::string &t) {
        std::cout<<"push_back"<<std::endl;
        data->push_back(t);};
    void pop_back();
    std::string& front();
    std::string& back();
    std::vector<std::string>::iterator begin();
    std::vector<std::string>::iterator end();

private:
    //使用shared_ptr管理裝入的容器類型
    std::shared_ptr<std::vector<std::string>> data;
    //檢查是否合法。data[i]不合法時(shí)拋出異常
    void check(size_type i, const std::string &msg) const;
};

//類外定義構(gòu)造函數(shù),類成員寫在函數(shù)體之前、函數(shù)名的冒號(hào)之后

StrBlob::StrBlob(): data(std::make_shared<std::vector<std::string>>()) {};

StrBlob::StrBlob(std::initializer_list<std::string> il):
    data(std::make_shared<std::vector<std::string>>(il)) {};

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

//類外定義的成員方法

void StrBlob::pop_back() {
    check(0, "no element to pop");
    std::cout<<"pop_back"<<std::endl;
    data->pop_back();
}

std::string& StrBlob::front() {
    check(0, "no element in the front");
    std::cout<<"front"<<std::endl;
    return data->front();
}

std::string& StrBlob::back() {
    check(0, "no element in the back");
    std::cout<<"back"<<std::endl;
    return data->back();
}

std::vector<std::string>::iterator StrBlob::begin() {
    check(0, "no element");
    return data->begin();
}

std::vector<std::string>::iterator StrBlob::end() {
    check(0, "no element");
    return data->end();
}

int main(){
    StrBlob s1 = {"a", "b", "c"};
    s1.pop_back();
    //使用范圍for必須實(shí)現(xiàn)begin和end
    s1.push_back("d");
    std::ostream_iterator<std::string> a(std::cout, " ");
    for (auto j: s1)
        a = j;
}

直接管理內(nèi)存

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

自由空間分配的內(nèi)存是無名的,因此new無法為其分配的對(duì)象命名,而是返回一個(gè)指向該對(duì)象的指針

int* pi = new int;

內(nèi)置類型和組合類型通常被默認(rèn)初始化,因此值是ub。類類型使用默認(rèn)構(gòu)造函數(shù)初始化

string* ps = new string;
int* pi = new int;

可以使用直接初始化、列表初始化等構(gòu)造方式動(dòng)態(tài)分配對(duì)象。

int* p = new int(10);
std::string s = new string(10, '!');
auto v = new std::vector<int>{0, 1, 2, 3, 5};

也可以使用值初始化,加括號(hào)即可:

int* p1 = new int;          //默認(rèn)初始化
int* p2 = new int();        //值初始化
std::cout<<*p1<<std::endl;  //-842150451
std::cout<<*p2<<std::endl;  //0

若提供了括號(hào)包圍的初始化器,則可以使用auto。注意當(dāng)括號(hào)中只有單一初始化器才可以使用auto。

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

可以使用new動(dòng)態(tài)分配一個(gè)const對(duì)象。

int main(){
    int i = 1024;
    const auto* p = new int(i);
    std::cout<<typeid(*p).name()<<'\t'<<*p<<std::endl;
    //int     1024
}

內(nèi)存耗盡

當(dāng)某個(gè)程序用盡可用內(nèi)存,new會(huì)失敗。默認(rèn)情況下若new不出所需內(nèi)存空間則會(huì)拋出類型為bad_alloc的異常。可以改變使用new的方式阻止其拋出異常:

#include <new>
int* p1 = new int;
int* p2 - new(nothrow) int 
//分配失敗時(shí)不throw而是返回一個(gè)空指針

這種形式的new稱為定位new(placement new),允許向new傳遞額外的參數(shù)。如此例中的nothrow。

bad_alloc和nothrow都定義在頭文件<new>中。

釋放動(dòng)態(tài)內(nèi)存

為了防止內(nèi)存耗盡,通過delete表達(dá)式(delete expression)將動(dòng)態(tài)內(nèi)存歸還給系統(tǒng)。delete接受一個(gè)指針,指向想要釋放的對(duì)象。
與new類似,該表達(dá)式執(zhí)行兩個(gè)動(dòng)作:銷毀給定的指針指向的對(duì)象;釋放對(duì)象對(duì)應(yīng)的內(nèi)存。

指針值和delete

傳遞給delete的指針必須指向動(dòng)態(tài)分配的內(nèi)存,或是一個(gè)空指針。釋放一塊并非new分配的內(nèi)存或?qū)⑾嗤闹羔樦刀啻吾尫攀荱B。
釋放一個(gè)空指針總是不會(huì)發(fā)生錯(cuò)誤。

int main(){
    int i = 0, *p = &i, *pn = nullptr;
    delete p;  //報(bào)錯(cuò)。不是動(dòng)態(tài)分配的地址
    delete pn; //總是不報(bào)錯(cuò)
}

動(dòng)態(tài)對(duì)象的生存期直到被釋放時(shí)為止

shared_ptr管理的內(nèi)存在銷毀時(shí)被自動(dòng)釋放。但對(duì)于內(nèi)置指針管理的內(nèi)存,在顯式釋放之前都是存在的。就算離開作用域,該處的內(nèi)存依然未被釋放。

int main() {
    int ** addr;
    int* p = new int;
    {
        int* ps = new(std::nothrow) int(100);
        p = ps;
        addr = &ps;
    }
    std::cout<<*p<<**addr<<std::endl;  //原地址存放的值依然存在
}

delete之后則上述*p**addr均變?yōu)閡b:

int main() {
    int ** addr;
    int* p = new int;
    {
        int* ps = new(std::nothrow) int(100);
        p = ps;
        addr = &ps;
        delete ps;
    }
    std::cout<<*p<<**addr<<std::endl;  //兩個(gè)ub值
}

delete之后重置指針值(僅提供有限的保護(hù))

當(dāng)delete一個(gè)指針后,指針值變?yōu)闊o效,但指針依然保存著被釋放的動(dòng)態(tài)內(nèi)存地址。delete之后該指針成為了空懸指針(dangling pointer),即指向一塊曾保存對(duì)象而已經(jīng)無效的內(nèi)存的指針。

空懸指針具有未初始化指針的所有缺點(diǎn)。

避免方式為:在指針即將離開其作用域之前釋放掉所關(guān)聯(lián)的內(nèi)存。若需要保留指針本身,則需在delete之后將nullptr賦予指針。

但是如此操作僅對(duì)該指針有效。若存在多個(gè)指針指向一塊內(nèi)存地址的情況,則對(duì)其他指針無效。例如:

int* p = new int(0);
auto q = p;
delete p;
p = nullptr;
//此時(shí)q依然是空懸指針

結(jié)合使用shared_ptr和new

不初始化的智能指針是一個(gè)空指針。
此外,可以使用new返回的指針來初始化智能指針。

接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit的,因此需要直接初始化。不能直接將內(nèi)置指針轉(zhuǎn)化為智能指針:

shared_ptr<int> p1 = new int(100);  //錯(cuò)誤
shared_ptr<int> p2(new int(100));   //正確:直接初始化

也可以使用make_shared(相當(dāng)于僅僅用了new出的指針的對(duì)象本身而不是這個(gè)指針),注意類型:

int* p = new int(42);
auto a = std::make_shared<int>(*p);   //只是利用了*p的值
std::shared_ptr<int> b(p);

另外,若要返回一個(gè)shared_ptr,在return語句中的正確寫法為:

return shared_ptr<int>(new int(p))

而不是簡單的return new int(p)

定義和改變shared_ptr的其他方法

- -
shared_ptr<T> p(q) p管理內(nèi)置指針q指向的對(duì)象;q必須指向new分配到內(nèi)存并且能轉(zhuǎn)換為T*類型
shared_ptr<T> p(u) p從unique_ptr u處接管了對(duì)象所有權(quán):將u置空
shared)ptr<T> p(q, d) p接管了內(nèi)置指針q指向的對(duì)象的所有權(quán),q必須能轉(zhuǎn)換為T*類型。p將使用可調(diào)用對(duì)象d代替delete
shared_ptr<T> p(p2, d) p是shared_ptr p2的拷貝,使用可調(diào)用對(duì)象d代替delete
p.reset() | p.reset(q) | p.reset(q, d) 若p是唯一指向其對(duì)象的shared_ptr,釋放此對(duì)象。若傳遞了q,則令p指向q,否則置空。若傳遞了d,則會(huì)調(diào)用可調(diào)用對(duì)象d而不是delete。

不要混合使用智能指針和普通指針

shared_ptr可以協(xié)調(diào)對(duì)象的析構(gòu),但僅限于自身的拷貝之間。因此推薦使用make_shared而不是new。這樣就能在分配對(duì)象的同時(shí)將shared_ptr與之綁定,避免無意中將同一塊內(nèi)存綁定到多個(gè)獨(dú)立創(chuàng)建的shared_ptr上。

考慮如下函數(shù):

void process<shared_ptr<T> ptr>{
    //do something
};  //離開作用域即被銷毀

該函數(shù)的傳參屬于傳值方式,實(shí)參被拷貝到ptr中,會(huì)增加引用計(jì)數(shù)。若傳遞給該函數(shù)一個(gè)shared_ptr:

shared_ptr<T> p;  //引用計(jì)數(shù)為1
precess(p);       //在拷貝傳參時(shí),引用計(jì)數(shù)+1,為2
auto i = *p;      //正確,p的引用計(jì)數(shù)為1

若傳遞一個(gè)由內(nèi)置指針轉(zhuǎn)化了的shared_ptr:

T* x(new T());
process(x);                  //錯(cuò)誤,參數(shù)類型不一致
process(shared_ptr<T>(x));   //正確,但括號(hào)內(nèi)語句作為臨時(shí)指針,在該表達(dá)式結(jié)束后就被銷毀
auto j = *x;      //錯(cuò)誤:x已經(jīng)是空懸指針

將一個(gè)shared_ptr綁定到一個(gè)普通指針時(shí),內(nèi)存的管理責(zé)任已經(jīng)屬于該shared_ptr,一旦這樣做了就不應(yīng)該再使用內(nèi)置指針訪問該地址。

不要使用get初始化另一個(gè)智能指針或?yàn)橹悄苤羔樫x值

智能指針類型定義了名為get的函數(shù),返回一個(gè)內(nèi)置指針,該內(nèi)置指針指向智能指針指向的對(duì)象。

  • get的設(shè)計(jì)情況是:需要向不能使用智能指針的代碼傳遞一個(gè)內(nèi)置指針。也就是將指針的訪問權(quán)限傳遞給代碼

  • 使用get返回的指針的代碼不能delete該指針。因此只有在確定代碼不delete指針的情況下才能使用get。

  • 編譯器不會(huì)報(bào)錯(cuò),但不能將另一個(gè)智能指針也綁定到get返回的這個(gè)內(nèi)置指針。永遠(yuǎn)不要用get初始化另一個(gè)智能指針或?yàn)榱硪粋€(gè)智能指針賦值

其他shared_ptr操作

可以使用reset將新的指針賦予一個(gè)shared_ptr。與賦值類似,會(huì)更新引用計(jì)數(shù)。

reset常和unique一起使用來控制多個(gè)shared_ptr共享的對(duì)象。在改變底層對(duì)象之前檢查自己是否是當(dāng)前對(duì)象僅有的用戶。若不是,則制作一份新的拷貝。

if(!p.unique()) p.reset(new string(*p)); //用對(duì)象的值分配新的拷貝,而不是指向原來的對(duì)象
*p += newVal;  //拷貝后改變對(duì)象的值

另外,reset不接受智能指針作為參數(shù),因此下列操作非法:

b.reset(std::make_shared<int>(*b));
b.reset(std::shared_ptr<int>(*b));

智能指針和異常

異常處理程序能在異常發(fā)生后零程序繼續(xù),而該類程序需要確保在異常發(fā)生后資源能被正確釋放。一個(gè)簡單的確保資源正常釋放的方法就是使用智能指針。

函數(shù)退出的兩種可能:正常處理結(jié)束、發(fā)生異常。兩種情況都會(huì)銷毀局部對(duì)象。

如果使用智能指針,即使程序塊過早結(jié)束也能確保在內(nèi)存不再需要時(shí)將其釋放:

void foo(){
    shared_ptr<int> sp(new int(42)); 
    //假設(shè)該處拋出一個(gè)未捕獲的異常
}//函數(shù)結(jié)束后自動(dòng)釋放內(nèi)存

與之相對(duì),發(fā)生異常時(shí)直接管理的靜態(tài)內(nèi)存即使發(fā)生異常也不會(huì)在delete之前不會(huì)自動(dòng)釋放。

智能指針和啞類

析構(gòu)函數(shù)負(fù)責(zé)清理對(duì)象使用的資源。但是并非所有類都良好定義了析構(gòu)函數(shù),因此需要用戶顯式釋放使用的資源。若在資源分配和釋放之間發(fā)生異常,則程序會(huì)發(fā)生資源泄漏。

對(duì)于沒有析構(gòu)函數(shù)的類,使用智能指針相當(dāng)有效。

使用自己的釋放操作

在shared_ptr銷毀時(shí)默認(rèn)對(duì)管理的指針進(jìn)行delete操作,但可以定義一個(gè)刪除器(deleter)代替默認(rèn)的delete操作。例如:

void end_connection(connection* p){disconnect(*p);}

該函數(shù)接受一個(gè)connection類的指針,來進(jìn)行指定的釋放操作。
在創(chuàng)建shared_ptr時(shí)即可傳遞一個(gè)指向刪除器函數(shù)的參數(shù):

void f(destination& d){
    connection c = connect(&d);
    shared_ptr<connection> p (&c, end_connection);
    //使用connection類
    //即使程序異常也能正常釋放
}

unique_ptr

unique_ptr“擁有”其指向的對(duì)象,當(dāng)其被銷毀時(shí),指向的對(duì)象也被銷毀。

不同于shared_ptr,unique_ptr沒有類似make_shared的函數(shù)返回一個(gè)unique_ptr。因此定義時(shí)只能通過綁定一個(gè)new出的指針上。

類似shared_ptr,使用new返回的指針進(jìn)行初始化只能采用直接初始化。

由于獨(dú)占指向?qū)ο?,unique_ptr不支持普通的拷貝和賦值操作。

但是可以通過release或reset將指針的所有權(quán)從一個(gè)非const的unique_ptr轉(zhuǎn)移給另一個(gè)unique_ptr:

int main(){
    std::unique_ptr<int> p1(new int(1));
    //將所有權(quán)從p1轉(zhuǎn)給p2的同時(shí)p1置空
    std::unique_ptr<int> p2(p1.release());
    std::cout<<*p2<<std::endl;        //1
    std::unique_ptr<int> p3(new int(2));
    //將所有權(quán)從p3轉(zhuǎn)給p2并釋放p2原來指向的內(nèi)存
    p2.reset(p3.release());
    std::cout<<*p2<<std::endl;        //2
}

總之,對(duì)于unique_ptr,要交出所有權(quán)的一方作為參數(shù)均需要.release()。調(diào)用release會(huì)切斷unique_ptr和其原來管理的對(duì)象之間的聯(lián)系。release返回的指針通常被用來初始化另一個(gè)指針或者為另一個(gè)指針賦值。

- -
unique_ptr<T> u1 | unique_ptr<T, D> u2 空unique_ptr,可以指向類型T的對(duì)象。u1使用delete釋放它的指針;u2使用類型D的可調(diào)用對(duì)象釋放他的指針。
unique_ptr<T, D> u(d) 空unique_ptr,指向類型為T的對(duì)象,用類型為D的對(duì)象d代替delete
u = nullptr 釋放u指向的對(duì)象
u.release() u放棄控制權(quán)并置空,返回指向原對(duì)象的(內(nèi)置)指針
u.reset() | u.reset(q) | u.reset(nullptr) 若提供了內(nèi)置指針則指向該對(duì)象,否則置空

傳遞unique_ptr參數(shù)和返回unique_ptr

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

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

或返回一個(gè)局部對(duì)象的拷貝:

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

這是一種特殊“拷貝”。在13.6.2節(jié)詳述。

向unique_ptr傳遞刪除器

重載unique_ptr中的默認(rèn)刪除器和shared_ptr的機(jī)制不同。

重載unique_ptr的刪除器會(huì)影響到unique_ptr的類型和構(gòu)造,必須在尖括號(hào)中指名刪除器類型(shared_ptr僅影響構(gòu)造,尖括號(hào)內(nèi)類型只有一個(gè))。

weak_jptr

  • weak_ptr不控制所指向?qū)ο笊嫫?,它指向一個(gè)由shared_ptr管理的對(duì)象。
  • 將一個(gè)weak_ptr綁定到一個(gè)shared_ptr不會(huì)改變其引用次數(shù)。
  • 一旦最后一個(gè)指向?qū)ο蟮膕hared_ptr被銷毀,對(duì)象就被釋放,無論是否有weak_ptr。

因此,名稱符合其weak的特點(diǎn)

expression -
weak_ptr<T> w 空weak_ptr可以指向類型為T的對(duì)象
weak_ptr<T> w(sp) 與shared_sp指向相同對(duì)象的weak_ptr,T必須能轉(zhuǎn)換為sp指向的類型
w = p p可以是一個(gè)shared_ptr或者weak_ptr,復(fù)制后w和p共享對(duì)象
w.reset() 將w置空
w.use_count() 返回與w共享的shared_ptr的數(shù)量
w.expired() 如果w.use_count()為0則返回true,否則false
w.lock() 如果expired為true則返回空shared_ptr,否則返回指向w的對(duì)象的shared_ptr

創(chuàng)建一個(gè)weak_ptr需要用一個(gè)shared_ptr進(jìn)行初始化:

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);

由于weak_ptr的對(duì)象可能不存在,不能直接使用其訪問對(duì)象,而是利用lock檢查指向的對(duì)象是否依然存在。如果返回的shared_ptr存在,則其指向的底層對(duì)象也一直存在。

核查指針類

嘗試為StrBlob定義一個(gè)伴隨指針類StrBLobPtr,該類會(huì)保存一個(gè)weak_ptr指向StrBlob的data成員,這是初始化時(shí)提供的。通過使用weak_ptr不會(huì)影響給定的StrBlob的生存期,但是可以阻止用戶訪問一個(gè)不存在的vector。

含有運(yùn)算符重載、shared_ptr、weak_ptr的“完全體”StrBlob和StrBlobPtr類:

#include <vector>
#include <iostream>
#include <memory>
#include <iterator>
#include <algorithm>
#include <string>


class StrBlobPtr;
class StrBlob{
public:
    friend class StrBlobPtr;
    typedef std::vector<std::string>::size_type size_type;
    //默認(rèn)構(gòu)造函數(shù)
    StrBlob();
    //以接受初始化列表的構(gòu)造函數(shù)
    StrBlob(std::initializer_list<std::string> il);
    //基本方法
    [[nodiscard]] size_type size() const {return data->size();};
    [[nodiscard]] bool empty() const {return data->empty();};
    void push_back(const std::string &t) {
        std::cout<<"push_back"<<std::endl;
        data->push_back(t);};
    void pop_back();
    std::string& front();
    std::string& back();
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    //使用shared_ptr管理裝入的容器類型
    std::shared_ptr<std::vector<std::string>> data;
    //檢查是否合法。data[i]不合法時(shí)拋出異常
    void check(size_type i, const std::string &msg) const;
};

//類外定義構(gòu)造函數(shù),類成員寫在函數(shù)體之前、函數(shù)名的冒號(hào)之后

StrBlob::StrBlob(): data(std::make_shared<std::vector<std::string>>()) {};

StrBlob::StrBlob(std::initializer_list<std::string> il):
        data(std::make_shared<std::vector<std::string>>(il)) {};

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

//類外定義的成員方法

void StrBlob::pop_back() {
    check(0, "no element to pop");
    std::cout<<"pop_back"<<std::endl;
    data->pop_back();
}

std::string& StrBlob::front() {
    check(0, "no element in the front");
    std::cout<<"front"<<std::endl;
    return data->front();
}

std::string& StrBlob::back() {
    check(0, "no element in the back");
    std::cout<<"back"<<std::endl;
    return data->back();
}


class StrBlobPtr{
public:
    StrBlobPtr(): curr(0) {};
    StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) {};
    std::string& operator*() const;
    StrBlobPtr& operator++();
    bool operator==(StrBlobPtr&) const;
    bool operator!=(StrBlobPtr&) const;

private:
    [[nodiscard]]std::shared_ptr<std::vector<std::string>> check (std::size_t, const std::string&) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string& s) const {
    auto ret = wptr.lock();
    if(!ret) throw std::runtime_error("unbound StrBlobPtr");
    if(i >= ret->size()) throw std::out_of_range(s);
    return ret;
}

//重載運(yùn)算符方式
std::string& StrBlobPtr::operator*() const {
    auto p = check(curr, "dereference past end");   //是lock返回的指針
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::operator++() {
    check(curr, "increment past end of StrBlobPtr");  //已經(jīng)是尾后則不可遞增
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() {
    check(0, "no element");
    return StrBlobPtr(*this);
}

StrBlobPtr StrBlob::end() {
    check(0, "no element");
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

bool StrBlobPtr::operator==(StrBlobPtr& p) const{
    return this->curr == p.curr? true : false;
}

bool StrBlobPtr::operator!=(StrBlobPtr& p) const{
    return this->curr == p.curr? false : true;
}


int main(){
    StrBlob s1 = {"aa", "bb", "cc"};
    s1.pop_back();
    //使用范圍for必須實(shí)現(xiàn)begin和end成員
    s1.push_back("dd");

    auto sp = StrBlobPtr(s1, 0);
    //注意#include<string>
    std::cout<<*sp<<std::endl;
    ++sp;
    std::cout<<*sp<<std::endl;

    auto iter = s1.begin();
    //for(iter, s1.end(); iter != s1.end(); ++iter) std::cout<<*iter;
    //若使用范圍for需要重載指針的!=以及++
    for(auto& j: s1) std::cout<<j<<" ";
    std::cout<<std::endl;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容