一、簡介
C++智能指針shared_ptr是一種可以自動管理內(nèi)存的智能指針,它是C++11新增的特性之一。與傳統(tǒng)指針不同,shared_ptr可以自動釋放所管理的動態(tài)分配對象的內(nèi)存,并避免了手動釋放內(nèi)存的繁瑣操作,從而減少了內(nèi)存泄漏和野指針的出現(xiàn)。
shared_ptr是一個模板類,通過引用計數(shù)器實現(xiàn)多個智能指針共享對一個對象的所有權(quán)。每次復(fù)制一個shared_ptr對象時,該對象的引用計數(shù)器會增加1,當(dāng)最后一個shared_ptr對象被銷毀時,引用計數(shù)器減1,如果引用計數(shù)器變?yōu)?,則釋放所管理的對象的內(nèi)存。
使用shared_ptr需要包含頭文件,并且可以通過以下方式創(chuàng)建:
std::shared_ptr<int> p(new int(10));
上面的代碼創(chuàng)建了一個shared_ptr對象p,它指向一個動態(tài)分配的int類型對象,初始值為10。
在使用shared_ptr時,需要注意以下幾點:
-
不要使用裸指針來初始化shared_ptr,否則可能導(dǎo)致多次刪除同一個對象的情況。
-
避免在shared_ptr中存儲數(shù)組,因為shared_ptr只能處理單個對象的釋放,而不能正確地處理數(shù)組的銷毀。
-
可以通過自定義刪除器(deleter)來實現(xiàn)對對象的特定方式的釋放。
-
shared_ptr可以作為函數(shù)參數(shù)傳遞,但要注意避免循環(huán)引用的問題,否則會導(dǎo)致內(nèi)存泄漏。
shared_ptr是一種方便且安全的內(nèi)存管理工具,能夠有效地避免內(nèi)存泄漏和野指針的出現(xiàn)。
二、底層原理
2.1、引用計數(shù)
shared_ptr的核心是引用計數(shù)技術(shù)。在每個shared_ptr對象中,都有一個指向所管理對象的指針和一個整型計數(shù)器。這個計數(shù)器統(tǒng)計有多少個shared_ptr對象指向該所管理對象。當(dāng)一個新的shared_ptr對象指向同一塊內(nèi)存時,該內(nèi)存的引用計數(shù)就會增加1。當(dāng)一個shared_ptr對象不再指向該內(nèi)存時,該內(nèi)存的引用計數(shù)就會減少1。當(dāng)引用計數(shù)為0時,說明沒有任何shared_ptr對象指向該內(nèi)存,此時該內(nèi)存將會被自動釋放。
2.2、shared_ptr的構(gòu)造和析構(gòu)
-
shared_ptr的構(gòu)造函數(shù)需要一個指針作為參數(shù),該指針指向要被管理的對象。當(dāng)一個新的shared_ptr對象被創(chuàng)建時,它會嘗試增加所管理對象的引用計數(shù)。如果該對象還未被其他shared_ptr對象管理,則會創(chuàng)建一個新的引用計數(shù),并將其設(shè)置為1。否則,它會與已經(jīng)存在的shared_ptr對象共享同一個引用計數(shù)。
-
shared_ptr的析構(gòu)函數(shù)會嘗試減少所管理對象的引用計數(shù)。如果引用計數(shù)變成0,則會自動釋放所管理對象的內(nèi)存。
-
shared_ptr的控制塊(包含引用計數(shù)和刪除器等信息)會在最后一個指向所管理對象的shared_ptr析構(gòu)時被釋放。當(dāng)引用計數(shù)減為0時,就說明沒有任何shared_ptr對象指向該所管理對象了,此時shared_ptr會自動調(diào)用刪除器,并釋放掉控制塊。由于shared_ptr可以共享同一個控制塊,因此只有所有shared_ptr對象都析構(gòu)后,控制塊才能被釋放。如果一個shared_ptr對象使用reset()方法手動解除與所管理對象的關(guān)聯(lián),也會相應(yīng)地減少引用計數(shù),當(dāng)引用計數(shù)變成0時,控制塊也會被釋放。
2.3、shared_ptr的共享和拷貝
shared_ptr可以與其他shared_ptr對象共享同一個指向?qū)ο蟮闹羔槨.?dāng)一個shared_ptr對象被復(fù)制時,它所管理的對象的引用計數(shù)也會增加1。因此,任何一個持有相同指針的shared_ptr對象都可以通過更改其所管理對象的狀態(tài)來影響所有其他shared_ptr對象。
2.4、循環(huán)引用問題
如果一個對象A包含指向另一個對象B的shared_ptr,而對象B也包含指向?qū)ο驛的指針,則這兩個對象將形成循環(huán)引用。在這種情況下,可能會出現(xiàn)內(nèi)存泄漏。
shared_ptr 循環(huán)引用問題是指兩個或多個對象之間通過shared_ptr相互引用,導(dǎo)致對象無法被正確釋放,從而造成內(nèi)存泄漏。
常見的情況是兩個對象A和B,它們的成員變量互相持有了對方的shared_ptr。當(dāng)A和B都不再被使用時,它們的引用計數(shù)不會降為0,無法被自動釋放。
解決這個問題的方法有以下幾種:
1.打破循環(huán)引用:可以通過將shared_ptr改為weak_ptr來解決。weak_ptr是一種弱引用,不會增加對象的引用計數(shù),在對象釋放時會自動設(shè)置為nullptr??梢允褂脀eak_ptr.lock()方法來獲取對象的shared_ptr,當(dāng)對象已經(jīng)釋放時會返回一個空shared_ptr。
2.使用std::enable_shared_from_this:如果其中一個對象A需要獲取對另一個對象B的shared_ptr,可以讓對象B繼承std::enable_shared_from_this,并在A中使用shared_from_this()方法獲取B的shared_ptr,這樣就不會形成循環(huán)引用。
3.手動析構(gòu):如果無法修改代碼結(jié)構(gòu)或者無法使用前兩種方法解決問題,可以使用手動析構(gòu)的方式來釋放對象。通過調(diào)用reset()方法手動釋放shared_ptr,確保引用計數(shù)降為0,對象會被正確釋放。
4.使用weak_ptr和shared_ptr組合:將兩個對象的循環(huán)引用中的一個改為weak_ptr,另一個仍使用shared_ptr。這樣可以避免循環(huán)引用導(dǎo)致的內(nèi)存泄漏。
三、shared_ptr的使用
創(chuàng)建shared_ptr
對象的語法有以下幾種方式:
- 通過new關(guān)鍵字創(chuàng)建
std::shared_ptr<int> p(new int);
- 通過make_shared函數(shù)創(chuàng)建,該函數(shù)可以避免使用new關(guān)鍵字
std::shared_ptr<int> p = std::make_shared<int>();
- 傳遞指針和刪除器作為參數(shù)創(chuàng)建
void my_deleter(int* p) {
delete p;
}
std::shared_ptr<int> p(new int, my_deleter);
- 傳遞指針、刪除器和分配器作為參數(shù)創(chuàng)建
void my_deleter(int* p) {
delete p;
}
struct MyAllocator {
void* allocate(size_t size);
void deallocate(void* ptr, size_t size);
};
MyAllocator my_allocator;
std::shared_ptr<int> p(new int, my_deleter, my_allocator);
- 從另一個
shared_ptr
對象創(chuàng)建
std::shared_ptr<int> p1(new int);
std::shared_ptr<int> p2(p1);
- 使用移動語義從另一個
shared_ptr
對象創(chuàng)建
std::shared_ptr<int> p1(new int);
std::shared_ptr<int> p2(std::move(p1));
3.1、創(chuàng)建一個shared_ptr
使用shared_ptr創(chuàng)建一個智能指針非常簡單,只需要將一個指向動態(tài)分配內(nèi)存的裸指針作為參數(shù)傳遞給shared_ptr的構(gòu)造函數(shù)即可:
// 創(chuàng)建一個int類型的智能指針
std::shared_ptr<int> p(new int(10));
3.2、共享一個shared_ptr
shared_ptr可以與其他shared_ptr對象共享同一個指向?qū)ο蟮闹羔?,這樣就可以避免多次動態(tài)分配內(nèi)存和釋放內(nèi)存的問題。共享一個shared_ptr可以通過復(fù)制構(gòu)造函數(shù)和賦值運算符實現(xiàn):
// 復(fù)制構(gòu)造函數(shù)
std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2(p1);
// 賦值運算符
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p4;
p4 = p3;
注意:共享一個shared_ptr會增加所管理對象的引用計數(shù)。因此,任何一個持有相同指針的shared_ptr對象都可以通過更改其所管理對象的狀態(tài)來影響所有其他shared_ptr對象。
3.3、使用刪除器
除了管理所分配的內(nèi)存外,shared_ptr還可以使用刪除器(deleter)來管理對象。刪除器是一個函數(shù)或者函數(shù)對象,用于在shared_ptr釋放所管理對象時執(zhí)行特定的操作。刪除器可以通過shared_ptr的模板參數(shù)指定:
// 使用Lambda表達式作為刪除器
std::shared_ptr<int> p(new int(10), [](int* p){ delete[] p; });
3.4、解除關(guān)聯(lián)
如果需要解除shared_ptr與所管理對象的關(guān)聯(lián),可以使用reset()方法:
std::shared_ptr<int> p(new int(10));
p.reset();
注意:當(dāng)調(diào)用reset()方法后,所管理對象的引用計數(shù)會減少1。如果引用計數(shù)變成0,則會自動釋放所管理對象的內(nèi)存。
3.5、使用示例
#include <memory>
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() { cout << "MyClass constructor" << endl; }
~MyClass() { cout << "MyClass destructor" << endl; }
void printInfo() { cout << "This is MyClass" << endl; }
};
int main() {
shared_ptr<MyClass> p1(new MyClass()); // 創(chuàng)建一個shared_ptr指向MyClass對象
shared_ptr<MyClass> p2 = p1; // p1和p2都指向同一個MyClass對象
p1->printInfo(); // 訪問MyClass對象的成員函數(shù)
p2.reset(); // 釋放p2所指向的MyClass對象
p1->printInfo(); // 由于p1仍然指向MyClass對象,所以此處輸出"This is MyClass"
return 0;
}
上述代碼中,通過調(diào)用shared_ptr<MyClass>
構(gòu)造函數(shù)創(chuàng)建了兩個指針p1和p2,并且它們都指向一個MyClass
對象。我們調(diào)用reset()
函數(shù)來釋放p2所指向的MyClass
對象,但是由于p1仍然指向該對象,所以在調(diào)用p1->printInfo()
時仍然輸出"This is MyClass"。當(dāng)程序結(jié)束時,p1所指向的MyClass
對象會被自動釋放。
可以看到,使用shared_ptr
可以很方便地避免內(nèi)存泄漏和懸空指針等問題。另外,需要注意的是,shared_ptr
指針之間的賦值和拷貝操作都會增加指向?qū)ο蟮囊糜嫈?shù),即使一個指針已經(jīng)釋放了它所指向的對象,只要其他指針還在使用該對象,該對象就不會被自動刪除。因此,在使用shared_ptr
時需要注意對象的生命周期,避免產(chǎn)生意外的副作用。
四、shared_ptr循環(huán)引用問題的解決方法
假設(shè)存在這樣的循環(huán)引用:
#include <memory>
class B; //前向聲明
class A {
public:
std::shared_ptr<B> b_ptr; // A類持有B類的shared_ptr
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr; // B類持有A類的shared_ptr
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此時a和b之間形成了循環(huán)引用,導(dǎo)致其引用計數(shù)一直不為0
return 0;
}
解決方法:
(1)使用weak_ptr打破循環(huán)引用: 將A類和B類中的shared_ptr改為weak_ptr,將對象引用改為弱引用,這樣不會增加對象的引用計數(shù),從而避免循環(huán)引用導(dǎo)致的內(nèi)存泄漏。在需要使用對象的地方,可以通過lock()方法將weak_ptr轉(zhuǎn)換為shared_ptr來進行使用,如果對象已被釋放,則返回空shared_ptr。
#include <memory>
class B; //前向聲明
class A {
public:
std::weak_ptr<B> b_ptr; // A類持有B類的weak_ptr
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_ptr; // B類持有A類的weak_ptr
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此時a和b之間形成了循環(huán)引用,但由于使用了weak_ptr,不會形成內(nèi)存泄漏
return 0;
}
(2)修改對象的引用關(guān)系: 考慮是否需要A類和B類之間互相持有shared_ptr的引用關(guān)系。如果某一方只需要單向引用,可以將其引用改為裸指針或者weak_ptr。這樣可以避免形成循環(huán)引用。
#include <memory>
class B; //前向聲明
class A {
public:
std::shared_ptr<B> b_ptr; // A類持有B類的shared_ptr
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_ptr; // B類持有A類的weak_ptr
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此時a和b之間形成了循環(huán)引用,但由于a->b_ptr改為shared_ptr,b->a_ptr改為weak_ptr,不會形成內(nèi)存泄漏
return 0;
}
(3)手動析構(gòu): 如果無法修改代碼結(jié)構(gòu)或者無法使用前兩種方法解決問題,可以使用手動析構(gòu)的方式來釋放對象。通過調(diào)用reset()方法手動釋放shared_ptr,確保引用計數(shù)降為0,對象會被正確釋放。
#include <memory>
class B; //前向聲明
class A {
public:
std::shared_ptr<B> b_ptr; // A類持有B類的shared_ptr
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr; // B類持有A類的shared_ptr
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
a.reset(); // 手動釋放A對象
b.reset(); // 手動釋放B對象
return 0;
}
(4)使用weak_ptr和shared_ptr組合: 將兩個對象的循環(huán)引用中的一個改為weak_ptr,另一個仍使用shared_ptr。這樣可以避免循環(huán)引用導(dǎo)致的內(nèi)存泄漏。
#include <memory>
class B; //前向聲明
class A {
public:
std::shared_ptr<B> b_ptr; // A類持有B類的shared_ptr
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_ptr; // B類持有A類的weak_ptr
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此時a和b之間形成了循環(huán)引用,但由于a->b_ptr使用shared_ptr,b->a_ptr使用weak_ptr,不會形成內(nèi)存泄漏
return 0;
}
總結(jié)
智能指針是C++中一種重要的語言機制,其中shared_ptr是最常用和最經(jīng)典的智能指針之一。
-
shared_ptr是一種引用計數(shù)的智能指針,可以共享同一個對象。
-
使用shared_ptr時,需要包含頭文件< memory >。
-
創(chuàng)建shared_ptr對象時,可以直接將原始指針作為參數(shù)傳遞給構(gòu)造函數(shù),也可以使用make_shared函數(shù)進行創(chuàng)建。
-
對象的引用計數(shù)會在shared_ptr對象初始化、復(fù)制、釋放時自動更新。
-
當(dāng)某個shared_ptr對象被銷毀時,它所指向的對象的引用計數(shù)會減少,如果引用計數(shù)為0,則該對象會被自動刪除。
-
通過get函數(shù)可以獲取shared_ptr對象所管理的原始指針。
-
通過reset函數(shù)可以重新綁定shared_ptr對象所管理的原始指針。
-
可以使用unique函數(shù)判斷shared_ptr對象是否唯一擁有原始指針。
-
通常情況下,shared_ptr對象應(yīng)該在棧上創(chuàng)建,而不是使用new運算符在堆上創(chuàng)建。
-
在多線程環(huán)境下使用shared_ptr時需要注意,需要采取線程安全措施,比如使用鎖來保證引用計數(shù)的正確性。
-
shared_ptr是C++11中STL的一部分,它是一個模板類,用于管理動態(tài)地分配對象的內(nèi)存。shared_ptr可以自動完成內(nèi)存管理,確保內(nèi)存被正確釋放,并且非常易于使用。
-
shared_ptr是一個強大的智能指針類,它利用引用計數(shù)技術(shù)來管理動態(tài)分配的對象的內(nèi)存。shared_ptr可以避免循環(huán)引用和內(nèi)存泄漏等問題,并且易于使用,是C++程序員必不可少的工具之一。文章來源:http://www.zghlxwxcb.cn/news/detail-420881.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-420881.html
到了這里,關(guān)于C++智能指針shared_ptr詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!