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)存的原因:
- 不知道自己需要多少對(duì)象
- 不知道對(duì)象的準(zhǔn)確類型
- 需要在多個(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;
}