C++11 智能指針 shared_ptr Written on 2023-01-16 個人學習智能指針記錄合集: C++11 智能指針 C++11 智能指針 shared_ptr C++11 智能指針 unique_ptr C++11 智能指針 weak_ptr std::shared_ptr 共 ...
C++11 智能指針 shared_ptr
Written on 2023-01-16
個人學習智能指針記錄合集:
std::shared_ptr
共用智能指針,也被稱為計數智能指針。
共用智能指針會記錄有多少個共用智能指針指向同一個對象,當這個數為 0 的時候,程式自動的預設釋放(析構)這個對象,記錄有多少個的這個方法叫做引用計數
。
共用智能指針可以有多個共用智能指針同時管理同一個對象。
舉個慄子
普通指針管理
#include <iostream>
#include <memory>
using namespace std;
class Person{
public:
Person(){ cout << "Constructor: person's age = " << m_age << endl; }
Person(int age) : m_age(age){ cout << "Constructor: person's age = " << m_age << endl; }
~Person(){ cout << "Destructor: person's age = " << m_age << endl; }
void getAge(){ cout << "Person's age = " << m_age << endl; }
private:
int m_age = 0;
};
int main()
{
{
Person *p = new Person(18);
}
cout << endl << "Before return" << endl;
return 0;
}
/** 輸出:
Constructor: person's age = 18
Before return
**/
依輸出可見,p
指向的對象並沒有被析構,因為並沒有析構函數中的列印,這造成了記憶體泄漏。
shared_ptr
智能指針管理
// ...
int main()
{
{
shared_ptr<Person> sPtr1 {new Person(18)};
}
cout << endl << "Before return" << endl;
return 0;
}
/** 輸出:
Constructor: person's age = 18
Destructor: person's age = 18
Before return
**/
依輸出可見,離開了程式塊的作用域後,析構函數中的列印體現出來了,sPtr1
管理的對象自動的被析構了。
獲取引用計數
long use_count() const noexcept;
當 shared_ptr
為 nullptr
時,返回為 0。
初始化
創建空管理的 shared_ptr
constexpr shared_ptr() noexcept;
constexpr shared_ptr(std::nullptr_t) noexcept;
std::nullptr_t
:空指針nullptr
創建對非數組對象和數組對象管理的 shared_ptr
template< class Y >
explicit shared_ptr( Y* ptr );
Y
:動態分配的對象的類型
// ...
int main()
{
shared_ptr<int> sPtr1; // 創建空管理的shared_ptr
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
shared_ptr<int> sPtr2(nullptr); // 創建空管理的shared_ptr
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
shared_ptr<int> sPtr3(new int(100)); // 創建對非數組對象管理的shared_ptr
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
shared_ptr<int> sPtr4(new int[10]); // 創建對數組對象管理的shared_ptr
cout << "sPtr4 use count = " << sPtr4.use_count();
return 0;
}
/** 輸出:
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 1
sPtr4 use count = 1
**/
解釋:
sPtr1
和sPtr2
都為nullptr
,故引用計數都為 0;
sPtr3
和sPtr4
所管理的對象,都只有一個 shared_ptr
管理,故引用計數都為 1。
創建指定刪除器的 shared_ptr
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
template< class Deleter >
shared_ptr( std::nullptr_t ptr, Deleter d );
template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );
template< class Deleter, class Alloc >
shared_ptr( std::nullptr_t ptr, Deleter d, Alloc alloc );
Deleter
:刪除器,在引用計數為 0 時,shared_ptr
會自動的執行刪除器Alloc
:分配器(Allocator)
- 預設刪除器:預設對指向的記憶體地址進行釋放,即析構掉這塊地址的內容
- 非數組類型,以
delete ptr
為預設刪除器 - 數組類型,以
delete[] ptr
為預設刪除器
- 非數組類型,以
- 指定刪除器:能夠自行指定引用計數為 0 時,要做什麼,比如有些場景不需要對指向的記憶體進行釋放,比如關閉指向文件,關閉指向套接字
- 指定刪除器的函數結構:
void deletePtr(T* p){ ... }
- 也可使用 Lambda表達式
- 指定刪除器的函數結構:
例,對於文件的使用場景,不是直接delete
文件,而是關閉文件:
// ...
void closeFile(FILE* fp)
{
if (fp == nullptr) return;
fclose(fp);
cout << "File closed" << endl;
}
int main()
{
FILE* fp = fopen("data.txt" ,"w");
shared_ptr<FILE> sfp{fp,closeFile};
if (sfp == nullptr)
cout << "Failed opened" << endl;
else
cout << "File opened" << endl;
return 0;
}
創建通過現有共用智能指針的 shared_ptr
主要是通過複製構造函數和移動構造函數std::move()
。
shared_ptr( const shared_ptr& r ) noexcept;
template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;
shared_ptr( shared_ptr&& r ) noexcept;
template< class Y >
shared_ptr( shared_ptr<Y>&& r ) noexcept;
例,
// ...
int main()
{
shared_ptr<int> sPtr1(new int(100));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl << endl;
// 創建通過現有現有共用智能指針的 shared_ptr
// 通過拷貝構造函數創建
shared_ptr<int> sPtr2(sPtr1);
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl << endl;
// 通過賦值運算符創建
shared_ptr<int> sPtr3 = sPtr1;
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl << endl;
// 通過移動構造函數創建
shared_ptr<int> sPtr4(move(sPtr1));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
std::shared_ptr<int> sPtr5 = move(sPtr2);
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl;
cout << "sPtr5 use count = " << sPtr5.use_count();
return 0;
}
/** 輸出:
sPtr1 use count = 1
sPtr1 use count = 2
sPtr2 use count = 2
sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr1 use count = 0
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 3
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 3
sPtr4 use count = 3
sPtr5 use count = 3
**/
解釋:
sPtr2
、sPtr3
都是通過對sPtr1
執行了複製構造函數,因此sPtr1
的引用計數一次增加 1;sPtr2
、sPtr3
同理。
通過move(sPtr1)
,使得sPtr1
釋放了被管理對象的所有權,此時sPtr1
被設置為nullptr
,因此sPtr1
引用計數為 0;sPtr2
同理。
通過 std::make_shared
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
T
:非數組,指針指向的數據類型Args&&... args
:T
的構造函數參數列表
這種初始化效率更高,在 C++17 之前的編譯器更安全
// ...
int main()
{
shared_ptr<int> sPtr1 = make_shared<int>(100);
shared_ptr<int> sPtr2(make_shared<int>(200));
shared_ptr<int> sPtr3{make_shared<int>(300)};
return 0;
}
通過 .reset( ptr )
使用.reset( ptr )
,使得釋放對shared_ptr
原管理對象的所有權,轉為對新對象管理的所有權。
void reset() noexcept;
template< class Y >
void reset( Y* ptr );
template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );
template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );
註意,.reset( ptr )
若傳入的所指向的對象已被占有,程式會異常。
// ...
int main()
{
shared_ptr<int> sPtr1(new int(100));
shared_ptr<int> sPtr2 = sPtr1;
shared_ptr<int> sPtr3 = sPtr2;
shared_ptr<int> sPtr4 = sPtr1;
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
sPtr4.reset();
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
sPtr3.reset(new int(100));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count();
std::shared_ptr<int> sPtr5;
sPtr5.reset(sPtr1.get()); // 異常
return 0;
}
/** 輸出:
sPtr1 use count = 4
sPtr2 use count = 4
sPtr3 use count = 4
sPtr4 use count = 4
sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 0
sPtr1 use count = 2
sPtr2 use count = 2
sPtr3 use count = 1
sPtr4 use count = 0
**/
解釋:
- 通過複製構造函數後,
sPtr1-4
的引用計數均為 4。 - 對
sPtr4
使用了reset()
後,釋放被管理對象的所有權,被管理對象的shared_ptr
個數減 1,sPtr4
被設置為nullptr
,同時,sPtr1-3
的引用計數均變為 3。 - 對
sPtr3
使用了reset(new int(100))
後,釋放原被管理對象的所有權,被管理對象的shared_ptr
個數減 1,sPtr4
被設置為新的管理對象int(100)
,同時,sPtr1-3
的引用計數均變為 2。 sPtr5
的reset
傳入的所指向的對象已被占有,程式異常,沒有正常結束。
.reset()
釋放被管理對象的所有權
用於取消shared_ptr
對管理對象的所有權;
當這個對象被shared_ptr
管理的數量為 0,會執行刪除器。
使用.reset()
,會使得shared_ptr
設置為nullptr
。
獲取原始儲存的指針
T* get() const noexcept;
解引用存儲的指針 operator* and operator->
可以像普通指針一樣,使用shared_ptr
對所管理的對象進行訪問。
// ...
int main()
{
shared_ptr<int> sPtr1 {new int(100)};
cout << *sPtr1 << endl << endl;
int *i = sPtr1.get();
cout << *i << endl;
cout << *sPtr1 << endl << endl;
*i = 200;
cout << *i << endl;
cout << *sPtr1 << endl << endl;
*sPtr1 = 300;
cout << *i << endl;
cout << *sPtr1;
return 0;
}
/** 輸出:
100
100
100
200
200
300
300
**/
直觀展示自動管理記憶體
// ...
int main()
{
shared_ptr<Person> sPtr1 {make_shared<Person>()};
shared_ptr<Person> sPtr2 {make_shared<Person>(18)};
shared_ptr<Person> sPtr3 {make_shared<Person>(22)};
shared_ptr<Person> sPtr4 = sPtr1;
sPtr1.reset();
sPtr2.reset();
sPtr3.reset(new Person(19));
cout << endl << "Before return" << endl;
return 0;
}
/** 輸出:
Constructor: person's age = 0
Constructor: person's age = 18
Constructor: person's age = 22
Destructor: person's age = 18
Constructor: person's age = 19
Destructor: person's age = 22
Before return
Destructor: person's age = 0
Destructor: person's age = 19
**/
解釋:
- 定義了三個
shared_ptr
:sPtr1
、sPtr2
、sPtr3
,它們管理的對象分別為age = 0
、age = 18
、age = 22
,列印了三行Person
對象的構造函數中的輸出 - 通過賦值運算符,使得
sPtr4
同時管理sPtr1
管理的對象 - 釋放
sPtr1
被管理對象的所有權,此時因為sPtr4
還在管理原sPtr1
管理的對象age = 0
,因此age = 0
對象並沒有被析構 - 釋放
sPtr2
被管理對象的所有權,此時因為age = 18
對象沒有任何shared_ptr
進行管理,age = 18
對象被析構,列印了age = 18
對象的析構函數中的輸出 - 釋放
sPtr3
被管理對象的所有權,sPtr3
轉為管理age = 19
的對象;是先構造age = 19
對象,後再釋放sPtr3
被管理對象的所有權,列印了age = 19
對象的構造函數中的輸出;此時因為sPtr3
原管理的age = 22
對象沒有任何shared_ptr
進行管理,age = 22
對象被析構,列印了age = 22
對象的析構函數中的輸出 - 在程式返回前
Before return
,管理age = 0
和age = 19
對象的智能指針sPtr4
和sPtr3
被析構,age = 0
和age = 19
對象沒有智能指針管理,age = 0
和age = 19
對象被析構,列印了它們對象的析構函數中的輸出。
別名
template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;
別名用於訪問類的成員變數。
我們需要訪問的是某個實例的成員,因此並不希望在使用這個成員的時候,對應的實例被銷毀了。
使用別名的shared_ptr
,增加了對這個實例的控制權,但仍然訪問的是成員。
// ...
struct Person{ int age = 18; };
struct Student{ Person person; };
int main(){
shared_ptr<Student> studentPtr{make_shared<Student>()};
cout << "studentPtr use count = " << studentPtr.use_count() << endl;
shared_ptr<Person> personPtr{studentPtr, &(studentPtr->person)};
cout << "studentPtr use count = " << studentPtr.use_count() << endl;
cout << personPtr->age << endl;
return 0;
}
/** 輸出:
studentPtr use count = 1
studentPtr use count = 2
18
**/
shared_ptr
與函數
按值傳遞
int main(){
auto func = [](shared_ptr<int> sPtr){
cout << "value = " << *sPtr << endl;
cout << "enter func: use count = " << sPtr.use_count() << endl;
};
auto sPtr = make_shared<int>(100);
cout << "init: use count = " << sPtr.use_count() << endl;
func(sPtr);
cout << "exit func: use count = " << sPtr.use_count() << endl;
return 0;
}
/** 輸出:
init: use count = 1
value = 100
enter func: use count = 2
exit func: use count = 1
**/
按值傳遞,函數會複製一份參數,因此傳入的sPtr
會被覆制一份,造成其對象的引用計數增加 1;
執行完這個函數,複製的那一份sPtr
被銷毀,使得其對象的引用計數減少 1;
按引用傳遞
int main(){
auto func = [](shared_ptr<int> &sPtr){
cout << "value = " << *sPtr << endl;
cout << "enter func: use count = " << sPtr.use_count() << endl;
};
auto sPtr = make_shared<int>(100);
cout << "init: use count = " << sPtr.use_count() << endl;
func(sPtr);
cout << "exit func: use count = " << sPtr.use_count() << endl;
return 0;
}
/** 輸出:
init: use count = 1
value = 100
enter func: use count = 1
exit func: use count = 1
**/
按引用傳遞,函數不會複製一份參數;
因此若函數內部無其它導致增加引用計數的操作,函數執行過程中引用計數都不會改變。
按引用傳遞,但為const
int main(){
auto func = [](const shared_ptr<int> &sPtr){
cout << "value = " << *sPtr << endl;
cout << "enter func: use count = " << sPtr.use_count() << endl;
sPtr.reset(); // error
sPtr.reset(new Person()); // error
sPtr.release(); // error
};
auto sPtr = make_shared<int>(100);
cout << "init: use count = " << sPtr.use_count() << endl;
func(sPtr);
cout << "exit func: use count = " << sPtr.use_count() << endl;
return 0;
}
使用const
的引用傳遞,不能改變shared_ptr
所管理的對象是哪一個,使用.reset()
等都會造成編譯錯誤。
返回值為shared_ptr
int main(){
auto createSPtr = [](int i) -> shared_ptr<Person>{
shared_ptr<Person> sPtr = make_shared<Person>(i);
cout << "age = " << i << " use count = " << sPtr.use_count() << endl;
return sPtr;
};
shared_ptr<Person> sPtr = createSPtr(100);
sPtr->getAge(); cout << "use count = " << sPtr.use_count() << endl;
// 用作鏈式函數
createSPtr(200)->getAge();
cout << endl << "Before main return" << endl;
return 0;
}
/** 輸出:
Constructor: person's age = 100
age = 100 use count = 1
Person's age = 100
use count = 1
Constructor: person's age = 200
age = 200 use count = 1
Person's age = 200
Destructor: person's age = 200
Before main return
Destructor: person's age = 100
**/
可見當用作鏈式函數時,使用完畢後,unique_ptr
會被銷毀,同時被管理的對象也被析構。
管理動態數組需要指定刪除器
shared_ptr
的預設刪除器不支持釋放數組對象,需要指定刪除器。
例,一維數組指定刪除器
// ...
shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; });
同時,也可以使用 std::default_delete<T>()
函數作為刪除器,這個函數內部的刪除功能是通過delete
釋放,T 為釋放什麼類型的記憶體的類型。
例,一維數組指定刪除器
// ...
shared_ptr<int> ptr(new int[10], default_delete<int[]>());
可以自己封裝模板函數來使 shared_ptr
支持釋放數組對象。
// ...
template <typename T>
shared_ptr<T> arrayShared_ptr(size_t size)
{
// 返回匿名對象
return shared_ptr<T>(new T[size], default_delete<T[]>());
}
int main()
{
shared_ptr<int> sPtr1 = arrayShared_ptr<int>(100);
shared_ptr<int> sPtr2 = arrayShared_ptr<char>(200);
return 0;
}
危險行為
仍然可使用delete
釋放智能指針管理的對象的地址
可以使用delete
釋放shared_ptr
管理的對象的地址,但是其它共用指針仍然可能會訪問這塊地址,若訪問了則會出現程式異常,因此應避免使用手動的delete
。
// ...
shared_ptr<int> sPtr1 {new int(100)};
shared_ptr<int> sPtr2 = sPtr1;
delete sPtr1.get();
cout << *sPtr2 << endl; // error,運行時異常
同時存在原始指針和智能指針
如果一個地址記憶體,同時有原始指針和shared_ptr
指向它,即使當所有shared_ptr
都被銷毀,原始指針還依然存在的情況下,這個地址記憶體仍然會被釋放,若再用原始指針去訪問這個記憶體地址就是訪問了一塊未知地址的內容。
// ...
int *i1 = new int{100};
shared_ptr<int> sPtr1{i1};
int *i2 = sPtr1.get();
cout << *i1 << endl;
cout << *i2 << endl << endl;
sPtr1.reset();
cout << *i1 << endl; // 危險行為
cout << *i2 << endl; // 危險行為
return 0;
/** 輸出:
100
100
1664688 // 這是隨機的,訪問了一塊未知地址的內容
1664688 // 這是隨機的,訪問了一塊未知地址的內容
**/
同時,無論是使用shared_ptr
,還是其它的智能指針,都應該避免與原始指針混用。
避免同時存在原始指針和智能指針的解決方案:
// ...
int *i = new int{100};
shared_ptr<int> sPtr1{i};
i = nullptr;
delete i;
cout << *sPtr1 << endl; // ok
使用一個原始指針初始化多個shared_ptr
不能使用一個原始指針初始化多個shared_ptr
。
// ...
int *i = new int{100};
shared_ptr<int> sPtr1{i};
shared_ptr<int> sPtr2{i}; // error 編譯通過 運行錯誤
最後
shared_ptr
由於使用引用計數,因此會造成額外的記憶體和性能開銷,因此在性能要求極為苛刻的情況下不適用。
unique_ptr
是 0 開銷的智能指針,也能夠自動管理記憶體,但不會造成性能損失。