本篇 ShengYu 將介紹 C++ 的 std::shared_ptr 用法,std::shared_ptr 是可以讓多個 std::shared_ptr 共享一份記憶體,並且在最後一個 std::shared_ptr 生命週期結束時時自動釋放記憶體,本篇一開始會先介紹原始指標與智慧型指標寫法上的差異,再來介紹如何開始使用智慧型指標,並提供一些範例參考。
需要引入的標頭檔:<memory>
,編譯需要支援 C++11
範例1. 原始指標宣告與智慧型指標宣告的比較
我們先來看看原始指標是怎麼寫跟怎麼用,如 UseRawPointer()
內容所示,
另外有的對應的智慧型指標的版本,如 UseSmartPointer()
內容所示,
1 | void UseRawPointer() { |
範例2. 開始使用智慧型指標
以下為 C++ shared_ptr 幾種初始化寫法,盡可能地使用 make_shared
而不是用 new,範例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 盡可能地使用 make_shared
auto sp1 = make_shared<Song>("The Beatles", "Hey Jude");
// 或者不使用 auto 像這樣寫
shared_ptr<Song> sp1 = make_shared<Song>("The Beatles", "Hey Jude");
// 下面這樣的寫法也可以,但會有一些副作用,記憶體配置上不連續,以及分開記憶體配置效能相對低弱
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song("Lady Gaga", "Poker Face"));
// When initialization must be separate from declaration, e.g. class members,
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//等於: shared_ptr<Song> sp5;
sp5 = make_shared<Song>("Avril Lavigne", "What The Hell");
在 Scott Meyers 大神的《Effective Modern C++》書裡的條款 21 也提到:「盡量用 std::make_shared 取代直接使用 new」
範例3. 手動釋放記憶體
std::shared_ptr 如果需要手動釋放記憶體的話,可以使用智慧型指標裡的 reset() 函式,這邊要注意的是使用”點“來存取 std::shared_ptr.reset()
,範例如下:1
2
3
4
5
6
7
8
9
10
11
12
13void SmartPointerDemo2() {
// Create the object and pass it to a smart pointer
std::shared_ptr<LargeObject> pLarge(new LargeObject());
// Call a method on the object
pLarge->DoSomething();
// 在離開函式前手動釋放記憶體
pLarge.reset();
// ...
}
重複釋放記憶體問題
不建議使用下列這種寫法,用原始指標變數(下例中的p)去建立 shared_ptr 這種寫法容易造成重複釋放記憶體問題,因為 new Object 完後有個原始指標變數 p 指向這個物件,容易造成其它地方再把 p 建立 shared_ptr,
之後 sp 釋放完該物件後(delete),sp2 又再次釋放完該物件,造成重複釋放記憶體,1
2
3
4
5
6{
Object *p = new Object();
std::shared_ptr<Object> sp(p);
// ...
std::shared_ptr<Object> sp2(p);
} // sp 釋放,表示 delete 該物件,再換 sp2 釋放,造成釋放重複記憶體
應該改成下列這種寫法,直接在 shared_ptr 建構子裡 new Object,而避免用原始指標變數去建立 shared_ptr,1
2
3{
std::shared_ptr<Object> sp(new Object());
} // sp 釋放
透過std::shared_ptr.get()
可以取得原始指標,大概有兩種情況會使用到,一種是需要呼叫傳統的api,會需要傳遞原始指標,另一種是直接用原始指標,範例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// g++ std-shared-ptr.cpp -o a.out -std=c++11
class LargeObject {
public:
void DoSomething() {
printf("DoSomething\n");
}
};
void LegacyLargeObjectFunction(LargeObject *lo) {
printf("LegacyLargeObjectFunction\n");
lo->DoSomething();
}
int main() {
printf("===1===\n");
// Create the object and pass it to a smart pointer
std::shared_ptr<LargeObject> pLarge(new LargeObject());
printf("===2===\n");
// Call a method on the object
pLarge->DoSomething();
printf("===3===\n");
// 傳遞原始指標給 legacy API
LegacyLargeObjectFunction(pLarge.get());
printf("===4===\n");
// 用原始指標去接
LargeObject *p = pLarge.get();
LegacyLargeObjectFunction(p);
printf("===5===\n");
return 0;
}
輸出如下1
2
3
4
5
6
7
8
9
10===1===
===2===
DoSomething
===3===
LegacyLargeObjectFunction
DoSomething
===4===
LegacyLargeObjectFunction
DoSomething
===5===
ShengYu 探索實驗1. 生命週期
看看生命週期以及印出這兩種的記憶體位置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// g++ std-shared-ptr-exp1.cpp -o a.out -std=c++11
class LargeObject {
public:
LargeObject() {
printf("LargeObject::LargeObject()\n");
}
~LargeObject() {
printf("LargeObject::~LargeObject()\n");
}
void DoSomething() {
printf("DoSomething, x=%d\n", x);
}
int x = 10;
};
int main() {
printf("===1===\n");
LargeObject *p = new LargeObject();
p->DoSomething();
printf("%p\n", p);
printf("===2===\n");
std::shared_ptr<LargeObject> pLarge(p);
pLarge->DoSomething();
printf("%p\n", pLarge.get());
printf("%p\n", p);
printf("===3===\n");
//delete p; // double free or corruption
printf("===4===\n");
return 0;
}
輸出如下1
2
3
4
5
6
7
8
9
10
11===1===
LargeObject::LargeObject()
DoSomething, x=10
0x1524030
===2===
DoSomething, x=10
0x1524030
0x1524030
===3===
===4===
LargeObject::~LargeObject()
1 | auto song1 = new Song("Village People", "YMCA"); |
shared_ptr 轉換成 unique_ptr 是不允許的,但是可以反過來 unique_ptr 轉成 shared_ptr,
unique_ptr 轉 shared_ptr 有兩種方式,一種是1
shared_ptr<Point> p = make_unique<Point>();
另一種是使用 move,1
2unique_ptr<Point> p1 = make_unique<Point>();
shared_ptr<Point> p2 = move(p1);
下一篇將會介紹 std::unique_ptr 用法
參考
[1] std::shared_ptr - cppreference.com
https://en.cppreference.com/w/cpp/memory/shared_ptr
[2] How to: Create and Use shared_ptr instances
https://docs.microsoft.com/zh-tw/cpp/cpp/how-to-create-and-use-shared-ptr-instances?view=vs-2019
圖表一與圖表二把 “參考計數器 ref count” 解釋地很好
[3] 智慧型指標 (新式 C++)
https://docs.microsoft.com/zh-tw/cpp/cpp/smart-pointers-modern-cpp?view=vs-2019
其它相關文章推薦
C/C++ 新手入門教學懶人包
std::unique_ptr 用法與範例
std::thread 用法與範例
std::deque 用法與範例
std::find 用法與範例
std::mutex 用法與範例
std::unordered_map 用法與範例
std::sort 用法與範例
std::random_shuffle 產生不重複的隨機亂數
std::async 用法與範例