【智能指針】shared_ptr基本用法和原理(共享指針)


目錄

shared_ptr基本用法

頭文件

聲明方法

增加計數

得到原指針

一個例子

shared_ptr

初始化shared_ptr對象

指定刪除器

shared_ptr 共享指針是怎樣計數的

std::shared_ptr 原理

std::shared_ptr使用注意事項


shared_ptr基本用法

頭文件

shared_ptr需要頭文件
#include <memory>

聲明方法

class A
{
 A()
 { cout << "A----" <<endl;}
}

//way1

A a;
auto sp1 = std::make_shared<int>(5);
auto sp11 = std::make_shared<A>(a);

//way2
int* p1 = new int[3];
std::shared_ptr<int> sp2(p1);

//way3
std::shared_ptr<A> sp3(new A);

//way4
auto sp31 = std::make_shared<A>(a);
std::shared_ptr<A> sp3(sp31);
    std::shared_ptr<int> p1(new int(1));  //方式1
    std::shared_ptr<int> p2 = p1;         //方式2
    std::shared_ptr<int> p3; 
    p3.reset(new int(1)); //方式3 reset,如果原有的shared_ptr不為空,會使原對象的引用計數減1
    std::shared_ptr<int> p4 = std::make_shared<int>(2); //方式4

一般來說 std::make_shared 是最推薦的一種寫法。

增加計數

被引用則會增加計數

std::shared_ptr<int> ptr2(sp2);//再次被引用則計數+1

在函數內改變計數,超過生命周期后計數會恢復,test函數內的p1析構了。

void test(int* ptr)
{
	std::shared_ptr<int> p1(ptr);
	int n = p1.use_count();
	std::cout << n << std::endl;
}

得到原指針

get()函數返回原指針

int* n3 = sp1.get();
std::cout << *(sp2.get()) << std::endl;

一個例子


#include "stdafx.h"
#include <iostream>
#include <memory>

void fun(std::shared_ptr<int> sp)
{
	std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n';
}

void test(int* ptr)
{
	std::shared_ptr<int> p1(ptr);
	int n = p1.use_count();
	std::cout << n << std::endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	auto sp1 = std::make_shared<int>(5);

	int* n3 = sp1.get();
	std::cout << *sp1 << '\n';;
	std::cout << "sp1.use_count() == " << sp1.use_count() << '\n';
	std::shared_ptr<int> p2(sp1);
	std::cout << "sp1.use_count() == " << sp1.use_count() << '\n';

	fun(sp1);
	fun(sp1);

	std::cout  <<"------------------"<< std::endl;
	std::cout << std::endl;
	int* p1 = new int[3];
	memset(p1, 0, sizeof(int) * 3);

	*p1 = 11;
	*(p1 + 1) = 22;
	*(p1 + 2) = 33;

	std::shared_ptr<int> sp2(p1);
	int n = sp2.use_count();
	std::cout << n << std::endl;

	std::shared_ptr<int> ptr2(sp2);
	n = ptr2.use_count();
	std::cout << n << std::endl;

	std::shared_ptr<int> ptr3 = sp2;
	n = ptr3.use_count();
	std::cout << n << std::endl;

	
	//std::cout << sp2 << std::endl;
	std::cout << *(sp2.get()) << std::endl;
	std::cout << *(sp2.get()+1) << std::endl;
	std::cout << *(sp2.get() + 2) << std::endl;
	return 0;
}


https://blog.csdn.net/Richelieu_/article/details/83548000

 

shared_ptr

std::shared_ptr采用引用計數,每一個shared_ptr的拷貝都指向相同的內容,當最后一個shared_ptr析構的時候,內存被釋放

初始化shared_ptr對象

#include<iostream>
#include<memory>
 
int main(){
    std::shared_ptr<int> p1(new int(1));  //方式1
    std::shared_ptr<int> p2 = p1;         //方式2
    std::shared_ptr<int> p3; 
    p3.reset(new int(1)); //方式3 reset,如果原有的shared_ptr不為空,會使原對象的引用計數減1
    std::shared_ptr<int> p4 = std::make_shared<int>(2); //方式4

//使用方法例子:可以當作一個指針使用
    std::cout << *p4 << std::endl;
    //std::shared_ptr<int> p4 = new int(1);
    if(p1) { //重載了bool操作符
        std::cout << "p is not null" << std::endl;
    }
    int* p = p1.get();//獲取原始指針 
    std::cout << *p << std::endl; 
}

指定刪除器

當使用shared_ptr刪除數組時,需要指定刪除器
常用的寫法有以下幾種

#include<iostream>
#include<memory>
template<typename T>
std::shared_ptr<T> make_shared_array(size_t size) {
    return std::shared_ptr<T>(new T[size], std::default_delete<T[]>());
} 
int main(){
    std::shared_ptr<int> p(new int[10], [](int* p){delete [] p;});      //lambda
    std::shared_ptr<int> p1(new int[10], std::default_delete<int[]>()); //指定默認刪除器
    std::shared_ptr<char> p2 = make_shared_array<char>(10);             //自定義泛型方法 
}

 

shared_ptr 共享指針是怎樣計數的

共享指針,即多個指針指向同一個內存;具體實現方式是采用的引用計數,即這塊地址上每多一個指針指向他,計數加一;

引用計數可以跟蹤對象所有權,並能夠自動銷毀對象。可以說引用計數是個簡單的垃圾回收體系。

 

std::shared_ptr 原理

 

智能指針是模板類而不是指針。創建一個智能指針時,必須指針可以指向的類型,<int>,<string> ……等。

智能指針實質就是重載了->和*操作符的類,由類來實現對內存的管理,確保即使有異常產生,也可以通過智能指針類的析構函數完成內存的釋放。

 

具體來說它利用了引用計數技術和 C++ 的 RAII(資源獲取就是初始化)特性。

先來說說 RAII,RAII 可以保證在任何情況下,使用對象時先構造對象,最后析構對象。

引用計數則是通過計算對裸指針引用次數來決定是否要釋放這個指針對象,比如這個代碼:

struct BigObj {
    BigObj() {
        std::cout << "big object has been constructed" << std::endl;
    }
    ~BigObj() {
        std::cout << "big object has been destructed" << std::endl;
    }
};
std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
std::shared_ptr<BigObj> sp2 = sp1;
std::shared_ptr<BigObj> sp3 = sp2;

 

創建智能指針 sp1 時,該智能指針管理了一個 BigObj 的裸指針,sp1 內部有一個關於裸指針的引用計數,這時 sp1 的引用計數是 1,因為只有一個智能指針引用這個裸指針。當把 sp1 賦值給 sp2 時,有兩個 shared_ptr 引用了該裸指針,因此引用計數就會加 1, std::shared_ptr<BigObj> 的引用計數就變為 2 了。同理,賦值給 sp3 時, std::shared_ptr<BigObj> 的引用計數就變為 3 了。

當引用計數減少為 0 時,智能指針就會去釋放所引用的裸指針了。那么如何讓引用計數減少呢?這里利用了 C++ 的 RAII 機制,我們只要在智能指針對象的析構函數里去減少引用計數就行了。

在 code2 目錄下新建一個 code2.cpp 文件:

#include <iostream>
#include <memory>

struct BigObj {
    BigObj() {
        std::cout << "big object has been constructed" << std::endl;
    }
    ~BigObj() {
        std::cout << "big object has been destructed" << std::endl;
    }
};

void test_ref() {
    std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
    std::cout << sp1.use_count() << std::endl;
    std::shared_ptr<BigObj> sp2 = sp1;
    std::cout << sp2.use_count() << std::endl;
    std::shared_ptr<BigObj> sp3 = sp2;
    std::cout << sp3.use_count() << std::endl;
    std::cout << sp1.use_count() << std::endl;
}

void test_ref1() {
    std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
    std::cout << sp1.use_count() << std::endl;

    {
        std::shared_ptr<BigObj> sp2 = sp1;
        std::cout << sp1.use_count() << std::endl;
    }
    std::cout << sp1.use_count() << std::endl;
    BigObj* ptr = sp1.get();

    sp1 = nullptr;
    std::cout << sp1.use_count() << std::endl;
}

int main() {

    test_ref();
    test_ref1();

}

 

編譯和運行代碼:在 build 目錄下執行

g++ ../code2.cpp -o code2 -std=c++11 && ./code2

 

輸出結果:

big object has been constructed
1
2
3
3
big object has been destructed
big object has been constructed
1
2
1
big object has been destructed
0

 

我們可以清晰地看到引用計數增加和減少的情況,當減少為 0 的時候就會釋放指針對象。

把 shared_ptr 設置為 nullptr 就可以讓 shared_ptr 去釋放所管理的裸指針。 通過 shared_ptr 的 get 方法可以獲取它所管理的裸指針。

 

使用shared_ptr避免了手動使用delete來釋放由new申請的資源,標准庫也引入了make_shared函數來創建一個shared_ptr對象,使用shared_ptr和make_shared,你的代碼里就可以使new和delete消失,同時又不必擔心內存的泄露。shared_ptr是一個模板類。

       

每一個shared_ptr的拷貝都指向相同的內存。在最后一個shared_ptr析構的時候, 內存才會被釋放。

可以通過構造函數賦值函數或者make_shared函數初始化智能指針。(沒有拷貝函數)

shared_ptr基於”引用計數”模型實現,多個shared_ptr可指向同一個動態對象,並維護一個共享的引用計數器,記錄了引用同一對象的shared_ptr實例的數量。當最后一個指向動態對象的shared_ptr銷毀時,會自動銷毀其所指對象(通過delete操作符)。

shared_ptr的默認能力是管理動態內存,但支持自定義的Deleter以實現個性化的資源釋放動作

 

最安全的分配和使用動態內存的方法是調用一個名為make_shared的標准庫函數。(關於make_shared:https://blog.csdn.net/bandaoyu/article/details/112197053)此函數在動態內存中分配一個對象並初始化它,返回指向此對象的shared_ptr。當要用make_shared時,必須指定想要創建的對象的類型,定義方式與模板類相同。在函數名之后跟一個尖括號,在其中給出類型。例如,調用make_shared<string>時傳遞的參數必須與string的某個構造函數相匹配。如果不傳遞任何參數,對象就會進行值初始化。 (make_shared<string>(“hello”))

當進行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其它shared_ptr指向相同的對象。

可以認為每個shared_ptr都有一個關聯的計數器,通常稱其為引用計數(reference count)。無論何時拷貝一個shared_ptr,計數器都會遞增。例如,當用一個shared_ptr初始化另一個shared_ptr,或將它作為參數傳遞給一個函數以及作為函數的返回值時,它所關聯的計數器就會遞增。當給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)時,計數器就會遞減。一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象。

當指向一個對象的最后一個shared_ptr被銷毀時,shared_ptr類會自動銷毀此對象。它是通過另一個特殊的成員函數析構函數(destructor)來完成銷毀工作的。類似於構造函數,每個類都有一個析構函數。就像構造函數控制初始化一樣,析構函數控制此類型的對象銷毀時做什么操作。shared_ptr的析構函數會遞減它所指向的對象的引用計數。如果引用計數變為0,shared_ptr的析構函數就會銷毀對象,並釋放它占用的內存。

如果將shared_ptr存放於一個容器中,而后不再需要全部元素,而只使用其中一部分,要記得用erase刪除不再需要的那些元素。

 

std::shared_ptr使用注意事項

使用 shared_ptr 需要注意下面這幾個問題:

  1. 不要讓一個裸指針初始化多個 shared_ptr ;
  2. 不要主動刪除 shared_ptr 所管理的裸指針
BigObj *p = new BigObj();
std::shared_ptr<BigObj> sp(p);
std::shared_ptr<BigObj> sp1(p);
delete p;

 

這里讓一個裸指針初始化兩個 shared_ptr ,會導致該指針被刪除兩次,這是錯誤的。另外,既然我們讓智能指針管理裸指針了,就不用再自己手動去刪除改指針了,否則也會導致兩次刪除,應該讓 shared_ptr 自動去刪除所管理的裸指針。(所以使用shared_ptr的賦值都是shared_ptrA=shatred_ptrB。在創建智能指針時候都是shared_ptr ptr(new xxx),或者shared_ptr ptr.reset(new xxx),不能new創建了對象p再用shared_ptr指向p這種分步驟的)

實例:https://blog.csdn.net/zhangruijerry/article/details/100927531

3、不要在函數實參中創建shared_ptr。
如下面中的例子。

//不同編譯器執行結果可能不同
//如果以new int -> 調用g() -> 創建shared_ptr的順序
//那么假如g()方法失敗,直接導致內存泄漏 
void f(shared_ptr<int>(new int), g())

4、多線程讀寫 shared_ptr 要加鎖

因為 shared_ptr 有兩個數據成員,讀寫操作不能原子化。

詳細解釋:http://www.cppblog.com/Solstice/archive/2016/04/01/197597.html 

 

std::shared_ptr使用注意事項2

  • 不要用一個原始指針初始化多個shared_ptr
  • 不要在函數實參中創建shared_ptr。

如下面中的例子。

//不同編譯器執行結果可能不同
//如果以new int -> 調用g() -> 創建shared_ptr的順序
//那么假如g()方法失敗,直接導致內存泄漏 
void f(shared_ptr<int>(new int), g())

 

  • 通過shared_from_this()返回this指針時,不要作為shared_ptr返回,因為this是一個裸指針,可能會導致重復析構。

如下面例子中,sp1和sp2重復析構A對象,導致錯誤。如果需要返回this指針,可以通過繼承enable_shared_from_this類,調用方法shared_from_this實現。如下面中注釋掉的寫法。
如果

#include<iostream>
#include<memory>
class A {
    public:
        std::shared_ptr<A> GetSelf() {
            return std::shared_ptr<A>(this);
        }
};
/*
class A :public std::enable_shared_from_this<A>{
    public:
        std::shared_ptr<A> GetSelf() {
            return shared_from_this();
        }
};
*/
int main(){
    std::shared_ptr<A> sp1(new A);
    std::shared_ptr<A> sp2 = sp1 -> GetSelf();
    
}
  • 要注意循環引用帶來的內存泄漏問題。如下面A與B循環引用,導致內存泄漏
#include<iostream>
#include<memory>
struct A;
struct B;
struct A {
    std::shared_ptr<B> bptr;
    ~A() {
        std::cout << "A is delete" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> aptr;
    ~B() {
        std::cout << "B is delete " << std::endl;
    }
};
int main(){
    std::shared_ptr<A> ap(new A);
    std::shared_ptr<B> bp(new B);
    ap->bptr = bp;
    bp->aptr = ap;
}

鏈接:https://www.jianshu.com/p/d304cfa56ca0

  •  shared_ptr的類型轉換不能使用一般的static_cast

 shared_ptr的類型轉換不能使用一般的static_cast,這種方式進行的轉換會導致轉換后的指針無法再被shared_ptr對象正確的管理。應該使用專門用於shared_ptr類型轉換的

static_pointer_cast<T>() 

const_pointer_cast<T>() 

dynamic_pointer_cast<T>()        

 

使用shared_ptr注意事項:

(1)、不要把一個原生指針給多個shared_ptr管理;

(2)、不要把this指針給shared_ptr;

(3)、不要在函數實參里創建shared_ptr;

(4)、不要不加思考地把指針替換為shared_ptr來防止內存泄漏,shared_ptr並不是萬能的,而且使用它們的話也是需要一定的開銷的;

(5)、環狀的鏈式結構shared_ptr將會導致內存泄漏(可以結合weak_ptr來解決);

(6)、共享擁有權的對象一般比限定作用域的對象生存更久,從而將導致更高的平均資源使用時間;

(7)、在多線程環境中使用共享指針的代價非常大,這是因為你需要避免關於引用計數的數據競爭;

(8)、共享對象的析構器不會在預期的時間執行;

(9)、不使用相同的內置指針值初始化(或reset)多個智能指針;

(10)、不delete get()返回的指針;

(11)、不使用get()初始化或reset另一個智能指針;

(12)、如果使用get()返回的指針,記住當最后一個對應的智能指針銷毀后,你的指針就變為無效了;

(13)、如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器。

下圖列出了shared_ptr支持的操作(來源於C++ Primer Fifth Edition 中文版):

下面是從其他文章中copy的測試代碼,詳細內容介紹可以參考對應的reference:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM