C++智能指针: shared_ptr 实现详解
文章目錄
- shared_ptr描述
- 聲明
- 作用
- 原理實(shí)現(xiàn)
- 函數(shù)使用
- 關(guān)于shared_ptr循環(huán)引用問(wèn)題
shared_ptr描述
聲明
shared_ptr屬于C++11特性中新加的一種智能指針,它的實(shí)現(xiàn)方式是模版類,頭文件<memory>
template <class T> class shared_ptr
所以使用shared_ptr的聲明方式即為
std::shared_ptr<type_id> statement 其中type_id為類型(可以是基本數(shù)據(jù)類型或者類),statement即為模版類對(duì)象
作用
shared_ptr 的理解如下:
- 使用一種叫做RAII(Resource Acquisition Is Initialization)的技術(shù)作為實(shí)現(xiàn)基礎(chǔ):
在對(duì)象構(gòu)造時(shí)獲取資源,接著控制對(duì)資源的訪問(wèn)使之在對(duì)象的生命周期內(nèi)始終保持有效,最后在對(duì)象析構(gòu)的時(shí)候釋放資源
raii技術(shù)的好處是:- 不需要顯式釋放資源
- 對(duì)象所擁有的資源在其生命周期內(nèi)始終有效
- 防止忘記調(diào)用delete釋放內(nèi)存或者程序異常退出時(shí)沒(méi)有釋放內(nèi)存。
- 同時(shí)它能夠?qū)⒅嫡Z(yǔ)義轉(zhuǎn)為引用語(yǔ)義(即shared_ptr可以讓多個(gè)指針指向相同的對(duì)象,共享同一塊地址空間),shared_ptr使用引用技術(shù)方式來(lái)統(tǒng)計(jì)當(dāng)前對(duì)象被引用的次數(shù),每一次執(zhí)行析構(gòu)函數(shù),引用計(jì)數(shù)就會(huì)-1,當(dāng)引用計(jì)數(shù)減為0時(shí)自動(dòng)刪除所指對(duì)象,回收對(duì)象空間。
原理實(shí)現(xiàn)
常用操作以及源碼實(shí)現(xiàn)如下:
類聲明如下
temple<typename T>
class SharedPtr {
public:...
private:T *_ptr;int *_refCount; //這里使用int型指針是為了保證拷貝構(gòu)造時(shí)同一個(gè)地址空間的引用計(jì)數(shù)增加
};
constructor構(gòu)造函數(shù),初始化的時(shí)候默認(rèn)引用計(jì)數(shù)為0
在使用普通指針初始化兩個(gè)shared_ptr的時(shí)候,兩個(gè)shared_ptr的引用計(jì)數(shù)都為1SharedPtr() : _ptr((T *)0), _refCount(0) {}
在進(jìn)行拷貝構(gòu)造的時(shí)候,使用shared_ptr去初始化另一個(gè)shared_ptr的時(shí)候引用計(jì)數(shù)會(huì)+1SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1)) { }SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount)) { }destructor析構(gòu)函數(shù),析構(gòu)的時(shí)候在指針不為空且引用計(jì)數(shù)為0的時(shí)候釋放空間~SharedPtr() {if (_ptr && --*_refCount == 0) {delete _ptr;delete _refCount;} }operator =當(dāng)使用一個(gè)shared_ptr給另一個(gè)shared_ptr賦值的時(shí)候這里需要注意- 由于指針指向發(fā)生變化,原來(lái)的_ptr指針的引用計(jì)數(shù)要–,且當(dāng)達(dá)到了0的時(shí)候要注意回收原來(lái)指針的空間
- _ptr又指向了新的_ptr,則新的_ptr指針的引用計(jì)數(shù)要++
SharedPtr &operator=(SharedPtr &other) {if(this==&other)return *this;//新指針引用計(jì)數(shù)要++ ++*other._refCount;//原指針引用計(jì)數(shù)要--,如果為0,則釋放空間if (--*_refCount == 0) {delete _ptr;delete _refCount;}//重新進(jìn)行指向 _ptr = other._ptr;_refCount = other._refCount;return *this; }operator*解引用運(yùn)算符,直接返回底層指針的引用,即共享的地址空間內(nèi)容T &operator*() {if (_refCount == 0)return (T*)0;return *_ptr; }operator ->指針運(yùn)算符T *operator->() {if(_refCount == 0)return 0;return _ptr; }
函數(shù)使用
主要案例如下
- 構(gòu)造函數(shù)
constructor,std::shared_ptr初始化案例如下,以及對(duì)應(yīng)的refcount打印
輸出如下#include <iostream> #include <memory>struct C {int* data;};int main () {std::shared_ptr<int> p1;//默認(rèn)構(gòu)造函數(shù),refcount為0std::shared_ptr<int> p2 (nullptr);//使用一個(gè)空的對(duì)象初始化時(shí)refcount也為0//普通指針初始化是引用計(jì)數(shù)為1,p3,p4std::shared_ptr<int> p3 (new int);std::shared_ptr<int> p4 (new int, std::default_delete<int>());//擁有allocator的時(shí)候初始化同樣引用計(jì)數(shù)為1//但是緊接著又用改智能指針拷貝構(gòu)造初始化其他智能指針p6,//所以最后其引用計(jì)數(shù)為2,p5std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());//這里p6本身為1,但是因?yàn)槭褂胹td::move去初始化p7,將p6指向轉(zhuǎn)給了p7//則p6智能指針recount--變?yōu)?,p7 ++由1變?yōu)?std::shared_ptr<int> p6 (p5);std::shared_ptr<int> p7 (std::move(p6));std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));std::shared_ptr<C> obj (new C);std::shared_ptr<int> p9 (obj, obj->data);std::cout << "use_count:\n";std::cout << "p1: " << p1.use_count() << '\n';std::cout << "p2: " << p2.use_count() << '\n';std::cout << "p3: " << p3.use_count() << '\n';std::cout << "p4: " << p4.use_count() << '\n';std::cout << "p5: " << p5.use_count() << '\n';std::cout << "p6: " << p6.use_count() << '\n';std::cout << "p7: " << p7.use_count() << '\n';std::cout << "p8: " << p8.use_count() << '\n';std::cout << "p9: " << p9.use_count() << '\n';return 0; }use_count: p1: 0 p2: 0 p3: 1 p4: 1 p5: 2 p6: 0 p7: 2 p8: 1 p9: 2 - 析構(gòu)函數(shù)
輸出如下// shared_ptr destructor example #include <iostream> #include <memory>int main () {auto deleter = [](int*p){std::cout << "[deleter called]\n"; delete p;};//使用特殊的delete函數(shù)去構(gòu)造,析構(gòu)的時(shí)候會(huì)執(zhí)行改delete 中l(wèi)amada表達(dá)式內(nèi)容.即構(gòu)造函數(shù)案例中的p5初始化方式std::shared_ptr<int> foo (new int,deleter);std::cout << "use_count: " << foo.use_count() << '\n';return 0; // [deleter called] }use_count: 1 [deleter called] - =賦值運(yùn)算符
輸出如下// shared_ptr::operator= example #include <iostream> #include <memory>int main () {std::shared_ptr<int> foo;std::shared_ptr<int> bar (new int(10));/*此時(shí)foo的引用計(jì)數(shù)為0,bar初始化后引用計(jì)數(shù)為1這里進(jìn)行賦值操作,即foo的指向發(fā)生了變化,指向了bar1.foo引用計(jì)數(shù)--,因?yàn)橐呀?jīng)為0了,此時(shí)直接釋放foo原來(lái)的空間2.bar引用計(jì)數(shù)++變?yōu)?3.更改foo的引用計(jì)數(shù)和bar引用計(jì)數(shù)相等,并使得foo指向bar.因?yàn)樗麄児蚕硗粋€(gè)空間執(zhí)行完之后fool和bar引用計(jì)數(shù)都相等,且解引用后數(shù)值都為0*/foo = bar; // copystd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';/*這里重新對(duì)bar進(jìn)行了初始化,即原先的指向發(fā)生了更改,所以它的引用計(jì)數(shù)--,并且內(nèi)容變?yōu)樾碌牡刂房臻g內(nèi)容20foo繼續(xù)指向原先空間,但是內(nèi)容并未變化。同時(shí)原先地址因?yàn)閎ar并不引用了,所以foo的引用計(jì)數(shù)--*/bar = std::make_shared<int> (20); // movestd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';std::unique_ptr<int> unique (new int(30));foo = std::move(unique); // move from unique_ptrstd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';return 0; }*foo: 10 foo.count 2 *bar: 10 bar.count 2 *foo: 10 foo.count 1 *bar: 20 bar.count 1 *foo: 30 foo.count 1 *bar: 20 bar.count 1 - shared_ptr::swap,交換兩個(gè)shared_ptr地址空間內(nèi)容,但并不破壞各自引用計(jì)數(shù)
輸出如下// shared_ptr::swap example #include <iostream> #include <memory>int main () {std::shared_ptr<int> foo (new int(10));std::shared_ptr<int> bar (new int(20));std::cout << "befor swap" << '\n';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';foo.swap(bar);std::cout << "after swap" << '\n';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';return 0; }befor swap *foo: 10 foo.count 1 *bar: 20 bar.count 1 after swap *foo: 20 foo.count 1 *bar: 10 bar.count 1 - shared_ptr::reset 替換所管理的對(duì)象
輸出如下,可以看到reset之后的地址發(fā)生了變化,即更改了指針的指向// shared_ptr::reset example #include <iostream> #include <memory>int main () {std::shared_ptr<int> sp; // emptysp.reset (new int); // 替換所管理對(duì)象,讓其更換地址指向std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';*sp=10;std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';sp.reset (new int); // 清除上一個(gè)指針指向的內(nèi)容,重新進(jìn)行更換std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';*sp=20;std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';sp.reset(); // deletes managed object//std::cout << *sp << " " << sp.use_count() << '\n';return 0; }0 1 0x434cd50 10 1 0x434cd50 0 1 0x434cd90 20 1 0x434cd90 - shared_ptr::get獲取初始指針
輸出如下:// shared_ptr::get example #include <iostream> #include <memory>int main () {int* p = new int (10);std::shared_ptr<int> a (p);std::shared_ptr<int> b (new int(20));//此時(shí)a和p共享同一個(gè)地址空間,所以a和p的內(nèi)容都為0,地址空間一樣if (a.get()==p)std::cout << "a and p point to the same location " << a << " " << p << '\n';std::cout << *a.get() << "\n";std::cout << *a << "\n";std::cout << *p << "\n";//此時(shí)a將共享空間釋放,重新更換指向b,但是*p為普通指針,并無(wú)法跟隨a更換指,所以p的地址內(nèi)容變?yōu)?a=b;std::cout << "a and p after copy " << a << " " << b << '\n';// three ways of accessing the same address:std::cout << *a.get() << "\n";std::cout << *a << "\n";std::cout << *p << "\n";return 0; }a and p point to the same location 0x21cf2f0 0x21cf2f0 10 10 10 a and p after copy 0x21cf330 0x21cf330 20 20 0
關(guān)于shared_ptr循環(huán)引用問(wèn)題
循環(huán)引用是指兩個(gè)shared_ptr初始化之后相互指向,在函數(shù)作用域結(jié)束之后由于兩個(gè)指針都保持相互的指向,引用計(jì)數(shù)都為1,此時(shí)各自占用的內(nèi)存空間無(wú)法釋放,最終產(chǎn)生內(nèi)存泄露
舉例如下:
#include<iostream>
#include<memory> using namespace std; class B;
class A{ public: shared_ptr<B> ptr_A; ~A(){ cout << "refcount " << ptr_A.use_count() << '\n';cout<<"~A()"<<endl; }
};
class B{ public: //shared_ptr<A> ptr_B;//當(dāng)采用shared_ptr指向A時(shí)會(huì)形成循環(huán)引用,則什么都不會(huì)輸出說(shuō)明對(duì)象沒(méi)有被析構(gòu),可怕的內(nèi)存泄露.... shared_ptr<A> ptr_B;//當(dāng)采用弱引用時(shí),避免了循環(huán)引用,有輸出,說(shuō)明對(duì)象被析構(gòu)了 ~B(){ cout << "refcount " << ptr_B.use_count() << '\n';cout<<"~B()"<<endl; }
};
int main(){ shared_ptr<A> a(new A); shared_ptr<B> b(new B); a->ptr_A=b; b->ptr_B=a;//若是循環(huán)引用:當(dāng)a、b退出作用域的時(shí)候,A對(duì)象計(jì)數(shù)不為1(b保留了個(gè)計(jì)數(shù)呢),同理B的計(jì)數(shù)也不為1,那么對(duì)象將不會(huì)被銷毀,內(nèi)存泄露了... cout << a.use_count() << " " << b.use_count()<< endl;return 0;
}
輸出如下,可以看到釋放的之前兩個(gè)智能指針的引用計(jì)數(shù)都為2,析構(gòu)的時(shí)候各自引用計(jì)數(shù)執(zhí)行–到·1,最終無(wú)法釋放
2 2
將classB中的shared_ptr更改為weak_ptr即可成功釋放
#include<iostream>
#include<memory> using namespace std; class B;
class A{ public: shared_ptr<B> ptr_A; ~A(){ cout << "refcount " << ptr_A.use_count() << '\n';cout<<"~A()"<<endl; }
};
class B{ public: //shared_ptr<A> ptr_B;//當(dāng)采用shared_ptr指向A時(shí)會(huì)形成循環(huán)引用,則什么都不會(huì)輸出說(shuō)明對(duì)象沒(méi)有被析構(gòu),可怕的內(nèi)存泄露.... weak_ptr<A> ptr_B;//當(dāng)采用弱引用時(shí),避免了循環(huán)引用,有輸出,說(shuō)明對(duì)象被析構(gòu)了 ~B(){ cout << "refcount " << ptr_B.use_count() << '\n';cout<<"~B()"<<endl; }
};
int main(){ shared_ptr<A> a(new A); shared_ptr<B> b(new B); a->ptr_A=b; b->ptr_B=a;//若是循環(huán)引用:當(dāng)a、b退出作用域的時(shí)候,A對(duì)象計(jì)數(shù)不為1(b保留了個(gè)計(jì)數(shù)呢),同理B的計(jì)數(shù)也不為1,那么對(duì)象將不會(huì)被銷毀,內(nèi)存泄露了... return 0;
}
輸出如下,調(diào)用析構(gòu)函數(shù)之前引一個(gè)智能指針的引用計(jì)已經(jīng)將為1,執(zhí)行析構(gòu)之后即為0
1 2
refcount 1
~A()
refcount 0
~B()
參考文檔:
http://www.cplusplus.com/reference/memory/shared_ptr/
https://www.xuebuyuan.com/3190713.html
總結(jié)
以上是生活随笔為你收集整理的C++智能指针: shared_ptr 实现详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 花仙子是谁画的呢?
- 下一篇: C++智能指针:weak_ptr实现详解