人生最大疑問(wèn)被解決!C++中智能指針的原理、使用、實(shí)現(xiàn)
來(lái)自:wxquare
鏈接:https://www.cnblogs.com/wxquare/p/4759020.html
目錄
理解智能指針的原理
智能指針的使用
智能指針的設(shè)計(jì)和實(shí)現(xiàn)
1、智能指針的作用
C++程序設(shè)計(jì)中使用堆內(nèi)存是非常頻繁的操作,堆內(nèi)存的申請(qǐng)和釋放都由程序員自己管理。程序員自己管理堆內(nèi)存可以提高了程序的效率,但是整體來(lái)說(shuō)堆內(nèi)存的管理是麻煩的,C++11中引入了智能指針的概念,方便管理堆內(nèi)存。使用普通指針,容易造成堆內(nèi)存泄露(忘記釋放),二次釋放,程序發(fā)生異常時(shí)內(nèi)存泄露等問(wèn)題等,使用智能指針能更好的管理堆內(nèi)存。
理解智能指針需要從下面三個(gè)層次:
1、從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術(shù)對(duì)普通的指針進(jìn)行封裝,這使得智能指針實(shí)質(zhì)是一個(gè)對(duì)象,行為表現(xiàn)的卻像一個(gè)指針。
2、智能指針的作用是防止忘記調(diào)用delete釋放內(nèi)存和程序異常的進(jìn)入catch塊忘記釋放內(nèi)存。另外指針的釋放時(shí)機(jī)也是非常有考究的,多次釋放同一個(gè)指針會(huì)造成程序崩潰,這些都可以通過(guò)智能指針來(lái)解決。
3、智能指針還有一個(gè)作用是把值語(yǔ)義轉(zhuǎn)換成引用語(yǔ)義。C++和Java有一處最大的區(qū)別在于語(yǔ)義不同,在Java里面下列代碼:
Animal a = new Animal();
Animal b = a;
你當(dāng)然知道,這里其實(shí)只生成了一個(gè)對(duì)象,a和b僅僅是把持對(duì)象的引用而已。但在C++中不是這樣,
Animal a;
Animal b = a;
這里卻是就是生成了兩個(gè)對(duì)象。
關(guān)于值語(yǔ)言參考這篇文章http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html
2、智能指針的使用
智能指針在C++11版本之后提供,包含在頭文件
2.1 shared_ptr的使用
shared_ptr多個(gè)指針指向相同的對(duì)象。shared_ptr使用引用計(jì)數(shù),每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存。每使用他一次,內(nèi)部的引用計(jì)數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),自動(dòng)刪除所指向的堆內(nèi)存。shared_ptr內(nèi)部的引用計(jì)數(shù)是線(xiàn)程安全的,但是對(duì)象的讀取需要加鎖。
初始化。智能指針是個(gè)模板類(lèi),可以指定類(lèi)型,傳入指針通過(guò)構(gòu)造函數(shù)初始化。也可以使用make_shared函數(shù)初始化。不能將指針直接賦值給一個(gè)智能指針,一個(gè)是類(lèi),一個(gè)是指針。例如std::shared_ptr
p4 = new int(1);的寫(xiě)法是錯(cuò)誤的 拷貝和賦值。 拷貝使得對(duì)象的引用計(jì)數(shù)增加1,賦值使得原對(duì)象引用計(jì)數(shù)減1,當(dāng)計(jì)數(shù)為0時(shí),自動(dòng)釋放內(nèi)存。后來(lái)指向的對(duì)象引用計(jì)數(shù)加1,指向后來(lái)的對(duì)象 get函數(shù)獲取原始指針 注意不要用一個(gè)原始指針初始化多個(gè)shared_ptr,否則會(huì)造成二次釋放同一內(nèi)存 注意避免循環(huán)引用,shared_ptr的一個(gè)最大的陷阱是循環(huán)引用,循環(huán),循環(huán)引用會(huì)導(dǎo)致堆內(nèi)存無(wú)法正確釋放,導(dǎo)致內(nèi)存泄漏。循環(huán)引用在weak_ptr中介紹。
#include <iostream>
#include <memory>
int main() {
{
int a = 10;
std::shared_ptr<int> ptra = std::make_shared<int>(a);
std::shared_ptr<int> ptra2(ptra); //copy
std::cout << ptra.use_count() << std::endl;
int b = 20;
int *pb = &a;
//std::shared_ptr<int> ptrb = pb; //error
std::shared_ptr<int> ptrb = std::make_shared<int>(b);
ptra2 = ptrb; //assign
pb = ptrb.get(); //獲取原始指針
std::cout << ptra.use_count() << std::endl;
std::cout << ptrb.use_count() << std::endl;
}
}
2.2 unique_ptr的使用
unique_ptr“唯一”擁有其所指對(duì)象,同一時(shí)刻只能有一個(gè)unique_ptr指向給定對(duì)象(通過(guò)禁止拷貝語(yǔ)義、只有移動(dòng)語(yǔ)義來(lái)實(shí)現(xiàn))。相比與原始指針unique_ptr用于其RAII的特性,使得在出現(xiàn)異常的情況下,動(dòng)態(tài)資源能得到釋放。unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時(shí)開(kāi)始,直到離開(kāi)作用域。離開(kāi)作用域時(shí),若其指向?qū)ο螅瑒t將其所指對(duì)象銷(xiāo)毀(默認(rèn)使用delete操作符,用戶(hù)可指定其他操作)。unique_ptr指針與其所指對(duì)象的關(guān)系:在智能指針生命周期內(nèi),可以改變智能指針?biāo)笇?duì)象,如創(chuàng)建智能指針時(shí)通過(guò)構(gòu)造函數(shù)指定、通過(guò)reset方法重新指定、通過(guò)release方法釋放所有權(quán)、通過(guò)移動(dòng)語(yǔ)義轉(zhuǎn)移所有權(quán)。
#include <iostream>
#include <memory>
int main() {
{
std::unique_ptr<int> uptr(new int(10)); //綁定動(dòng)態(tài)對(duì)象
//std::unique_ptr<int> uptr2 = uptr; //不能賦值
//std::unique_ptr<int> uptr2(uptr); //不能拷貝
std::unique_ptr<int> uptr2 = std::move(uptr); //轉(zhuǎn)換所有權(quán)
uptr2.release(); //釋放所有權(quán)
}
//超過(guò)uptr的作用域,內(nèi)存釋放
}
2.3 weak_ptr的使用
weak_ptr是為了配合shared_ptr而引入的一種智能指針,因?yàn)樗痪哂衅胀ㄖ羔樀男袨?,沒(méi)有重載operator*和->,它的最大作用在于協(xié)助shared_ptr工作,像旁觀者那樣觀測(cè)資源的使用情況。weak_ptr可以從一個(gè)shared_ptr或者另一個(gè)weak_ptr對(duì)象構(gòu)造,獲得資源的觀測(cè)權(quán)。但weak_ptr沒(méi)有共享資源,它的構(gòu)造不會(huì)引起指針引用計(jì)數(shù)的增加。使用weak_ptr的成員函數(shù)use_count()可以觀測(cè)資源的引用計(jì)數(shù),另一個(gè)成員函數(shù)expired()的功能等價(jià)于use_count()==0,但更快,表示被觀測(cè)的資源(也就是shared_ptr的管理的資源)已經(jīng)不復(fù)存在。weak_ptr可以使用一個(gè)非常重要的成員函數(shù)lock()從被觀測(cè)的shared_ptr獲得一個(gè)可用的shared_ptr對(duì)象, 從而操作資源。但當(dāng)expired()==true的時(shí)候,lock()函數(shù)將返回一個(gè)存儲(chǔ)空指針的shared_ptr。
#include <iostream>
#include <memory>
int main() {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()){
std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
//delete memory
}
2.4 循環(huán)引用
考慮一個(gè)簡(jiǎn)單的對(duì)象建?!议L(zhǎng)與子女:a Parent has a Child, a Child knowshis/her Parent。在Java 里邊很好寫(xiě),不用擔(dān)心內(nèi)存泄漏,也不用擔(dān)心空懸指針,只要正確初始化myChild 和myParent,那么Java 程序員就不用擔(dān)心出現(xiàn)訪問(wèn)錯(cuò)誤。一個(gè)handle 是否有效,只需要判斷其是否non null。
public class Parent
{
private Child myChild;
}
public class Child
{
private Parent myParent;
}
在C++里邊就要為資源管理費(fèi)一番腦筋。如果使用原始指針作為成員,Child和Parent由誰(shuí)釋放?那么如何保證指針的有效性?如何防止出現(xiàn)空懸指針?這些問(wèn)題是C++面向?qū)ο缶幊搪闊┑膯?wèn)題,現(xiàn)在可以借助smart pointer把對(duì)象語(yǔ)義(pointer)轉(zhuǎn)變?yōu)橹担╲alue)語(yǔ)義,shared_ptr輕松解決生命周期的問(wèn)題,不必?fù)?dān)心空懸指針。但是這個(gè)模型存在循環(huán)引用的問(wèn)題,注意其中一個(gè)指針應(yīng)該為weak_ptr。
原始指針的做法,容易出錯(cuò)
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
Child* myChild;
public:
void setChild(Child* ch) {
this->myChild = ch;
}
void doSomething() {
if (this->myChild) {
}
}
~Parent() {
delete myChild;
}
};
class Child {
private:
Parent* myParent;
public:
void setPartent(Parent* p) {
this->myParent = p;
}
void doSomething() {
if (this->myParent) {
}
}
~Child() {
delete myParent;
}
};
int main() {
{
Parent* p = new Parent;
Child* c = new Child;
p->setChild(c);
c->setPartent(p);
delete c; //only delete one
}
return 0;
}
循環(huán)引用內(nèi)存泄露的問(wèn)題
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
std::shared_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
}
void doSomething() {
if (this->ChildPtr.use_count()) {
}
}
~Parent() {
}
};
class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
}
}
~Child() {
}
};
int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 2
}
std::cout << wpp.use_count() << std::endl; // 1
std::cout << wpc.use_count() << std::endl; // 1
return 0;
}
正確的做法
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
//std::shared_ptr<Child> ChildPtr;
std::weak_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
}
void doSomething() {
//new shared_ptr
if (this->ChildPtr.lock()) {
}
}
~Parent() {
}
};
class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
}
}
~Child() {
}
};
int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 1
}
std::cout << wpp.use_count() << std::endl; // 0
std::cout << wpc.use_count() << std::endl; // 0
return 0;
}
3、智能指針的設(shè)計(jì)和實(shí)現(xiàn)
下面是一個(gè)簡(jiǎn)單智能指針的demo。智能指針類(lèi)將一個(gè)計(jì)數(shù)器與類(lèi)指向的對(duì)象相關(guān)聯(lián),引用計(jì)數(shù)跟蹤該類(lèi)有多少個(gè)對(duì)象共享同一指針。每次創(chuàng)建類(lèi)的新對(duì)象時(shí),初始化指針并將引用計(jì)數(shù)置為1;當(dāng)對(duì)象作為另一對(duì)象的副本而創(chuàng)建時(shí),拷貝構(gòu)造函數(shù)拷貝指針并增加與之相應(yīng)的引用計(jì)數(shù);對(duì)一個(gè)對(duì)象進(jìn)行賦值時(shí),賦值操作符減少左操作數(shù)所指對(duì)象的引用計(jì)數(shù)(如果引用計(jì)數(shù)為減至0,則刪除對(duì)象),并增加右操作數(shù)所指對(duì)象的引用計(jì)數(shù);調(diào)用析構(gòu)函數(shù)時(shí),構(gòu)造函數(shù)減少引用計(jì)數(shù)(如果引用計(jì)數(shù)減至0,則刪除基礎(chǔ)對(duì)象)。智能指針就是模擬指針動(dòng)作的類(lèi)。所有的智能指針都會(huì)重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動(dòng)銷(xiāo)毀。這主要是利用棧對(duì)象的有限作用域以及臨時(shí)對(duì)象(有限作用域?qū)崿F(xiàn))析構(gòu)函數(shù)釋放內(nèi)存。
1 #include <iostream>
2 #include <memory>
3
4 template<typename T>
5 class SmartPointer {
6 private:
7 T* _ptr;
8 size_t* _count;
9 public:
10 SmartPointer(T* ptr = nullptr) :
11 _ptr(ptr) {
12 if (_ptr) {
13 _count = new size_t(1);
14 } else {
15 _count = new size_t(0);
16 }
17 }
18
19 SmartPointer(const SmartPointer& ptr) {
20 if (this != &ptr) {
21 this->_ptr = ptr._ptr;
22 this->_count = ptr._count;
23 (*this->_count)++;
24 }
25 }
26
27 SmartPointer& operator=(const SmartPointer& ptr) {
28 if (this->_ptr == ptr._ptr) {
29 return *this;
30 }
31
32 if (this->_ptr) {
33 (*this->_count)--;
34 if (this->_count == 0) {
35 delete this->_ptr;
36 delete this->_count;
37 }
38 }
39
40 this->_ptr = ptr._ptr;
41 this->_count = ptr._count;
42 (*this->_count)++;
43 return *this;
44 }
45
46 T& operator*() {
47 assert(this->_ptr == nullptr);
48 return *(this->_ptr);
49
50 }
51
52 T* operator->() {
53 assert(this->_ptr == nullptr);
54 return this->_ptr;
55 }
56
57 ~SmartPointer() {
58 (*this->_count)--;
59 if (*this->_count == 0) {
60 delete this->_ptr;
61 delete this->_count;
62 }
63 }
64
65 size_t use_count(){
66 return *this->_count;
67 }
68 };
69
70 int main() {
71 {
72 SmartPointer<int> sp(new int(10));
73 SmartPointer<int> sp2(sp);
74 SmartPointer<int> sp3(new int(20));
75 sp2 = sp3;
76 std::cout << sp.use_count() << std::endl;
77 std::cout << sp3.use_count() << std::endl;
78 }
79 //delete operator
80 }
參考:
1、值語(yǔ)義:http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html
2、shared_ptr使用:http://www.cnblogs.com/jiayayao/archive/2016/12/03/6128877.html
3、unique_ptr使用:http://blog.csdn.net/pi9nc/article/details/12227887
4、weak_ptr的使用:http://blog.csdn.net/mmzsyx/article/details/8090849
5、weak_ptr解決循環(huán)引用的問(wèn)題:http://blog.csdn.net/shanno/article/details/7363480
6、C++面試題(四)——智能指針的原理和實(shí)現(xiàn)https://blog.csdn.net/worldwindjp/article/details/18843087#comments
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!