技術分享會之——智能指針

由於之前也只是瞭解智能指針,要我說估計只能說個它是幹什麼的,用不了幾分鐘。

昨天花了一天時間各種百度,算是對智能指針有了一點了解,這篇文章基本就是這次分享會的PPT的copy,沒有底層的東西,多是概念。

我覺得理解智能指針需要了解它發展的三個過程:起因,經過,結果。這篇文章主要講述的是起因,經過和結果等以後工作了,實際接觸了再說吧。


起因:

1.爲什麼需要智能指針

我們先看兩個例子

一:內存泄露

<pre name="code" class="cpp">//內存泄露
#include <stdio.h>
#include <iostream>
using namespace std;
class A
{
public:
    A(char *cstr);
    A(const A &ca);
    A();
    ~A();
    A operator=(const A &ca);
private:
    char *str;
};                        
A::A(char *cstr)            
{
    str = new char[20];
    strcpy(str, cstr);
}
A::A(const A &ca)           
{
    str = new char[20];
    strcpy(str, ca.str);
}
A A::operator=(const A &ca)   
{
    str = new char[20];      
    strcpy(str, ca.str);
    return str;
}
A::A() : str(new char[20])
{
    strcpy(str, "Welcome!");
}
A::~A()
{
    delete[] str;
    str = NULL;
}
int main(void)
{
    A a("Hello world!");
    A b(a);
    A c;
    c = b;
	
    return 0;
}

A A::operator=(constA &ca)   
{
    str =new char[20];      
    strcpy(str, ca.str);
    return str;
}
•罪魁禍首就是這段函數,當c=b的時候,就已經發生了內存泄露的隱患。
•原因:
–Ac 這句話調用了構造函數,已經在內存空間new了一塊空間。
–c=b 這句調用了操作符重載函數,同樣new了一塊空間,這樣上一次new的空間我們就找不到了。
–即使在析構的時候delet了第二次申請的空間,但是依然無法避免內存泄露的問題。

解決辦法:

一:
A::operator=(constA &ca)
{
strcpy(str, ca.str);
}

但是前提是我們已經申請了空間。
二:
A::operator=(const A &ca)   
{
delete[] str;
str = new char[20];
strcpy(str, ca.str);
}
這是個比較危險的方法,如果str沒有被初始化或者已經被delet過了,程序就會發生致命錯誤!
三:
A::operator=(const A &ca) 
{
if(str == NULL)
{          
str = new char[20];
}     
strcpy(str, ca.str);
}
這是最好的方法,可以在很多源碼中發現都是用了這種方法。但是需要注意的是str在沒有被分配前必須初始化爲NULL,因爲在未分配和初始化NULL的時候,str不一定是個空指針,還可能是一個野指針。

二:懸垂指針

#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace std;



int *p=NULL;
void fun()
{
	int i=10;
	p=&i;
}

int main()
{
	fun();
	cout<<"*p= "<<*p<<endl;//輸出p=10
	Sleep(1000);
	cout<<"*p= "<<*p<<endl;//輸出p=0
	return 0;
}
兩次輸出的值不一樣,因爲程序發生了懸垂指針的錯誤。具體和操作系統的堆棧管理方式有關,感興趣可以去百度~
在調用fun()函數的時候,指針P指向了這個模塊內的一個整型,當這個模塊剛剛返回,這個整型還是存在的,但是過了一會(具體多久還要看具體情況),它被系統釋放掉了,那麼P所指向的內容就不是我們能控制的了,這就是懸垂指針。
懸垂指針的定義:指向曾經存在的對象,但該對象已經不再存在了,此類指針稱爲懸垂指針。結果未定義,往往導致程序錯誤,而且難以檢測。

以上可以說是智能指針的起因了吧,我做過一些小項目,所以能深深體會到智能指針的必要性。有時候編寫程序的時候總會new一些東西,要麼忘了銷燬,要麼搞不清楚在哪裏銷燬,這也許和我經驗不足有關,但是難保一些大神們不會犯這種錯。

2.爲什麼不自建“智能”指針

這裏的爲什麼的主語-“我”特指像我這樣的新手。既然new和delete要成對的出現,那我們爲什麼不自己封裝一個類,創建的時候new,析構的時候delete呢?之所以特指,是因爲新手考慮問題都比較片面,這樣除了同樣會造成上面的內存泄露的問題,還會造成其他錯誤。
class intptr 
{ 
private: 
 int* m_p; 
public: 
 intptr(int* p){ m_p = p; } 
 ~intptr(){ delete m_p; } 
 int& operator*(){ return *m_p; } 
};
這就是上面想法的一個例子,當我們這樣調用的時候不會出現問題:
somefunction()
{
 intptr pi(new int); 
 *pi = 10; 
 int a = *pi; 
}
但是如果換種方式:
void somefunction()
{
 intptr pt1(new int);
 intptr pt2(new int);
 *pt1 = 10;
 pt2 = pt1;
}
問題就出現了,pt2指向了pt1所指向的內容,那麼pt2原來所指向的內容我們就無法獲取和銷燬了,這也是一種內存泄露。另外在程序塊結束後,這塊內存將要被銷燬兩次,這明顯是不符合邏輯的。


經過:

1.引用計數

由於以上問題,我們又引入了引用計數這個概念。
在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放佔有的資源。

2.幾種智能指針的概述

這裏只是概述,不說用法和分析源碼
智能指針有很多:
std::auto_ptr、
boost::scoped_ptr、
boost::shared_ptr、
boost::scoped_array、
boost::shared_array、
boost::weak_ptr、
boost::intrusive_ptr

auto_ptr

缺點OR特性:
不能作爲STL的成員(C++標準明確禁止這樣做,否則可能會碰到不可預見的結果)
不能共享所有權(不一定是缺點,有它一定的應用)
不能指向數組
不能通過賦值操作來初始化
std::auto_ptr<int> p(new int(42)); //OK
std::auto_ptr<int> p = new int(42); //ERROR

scoped_ptr

•boost::scoped_ptr的實現和std::auto_ptr非常類似,都是利用了一個棧上的對象去管理一個堆上的對象,從而使得堆上的對象隨着棧上的對象銷燬時自動刪除。不同的是,boost::scoped_ptr有着更嚴格的使用限制——不能拷貝。這就意味着:boost::scoped_ptr指針是不能轉換其所有權的。

•缺點OR特性:
–不能轉換所有權
boost::scoped_ptr所管理的對象生命週期僅僅侷限於一個區間(該指針所在的"{}"之間),無法傳到區間之外,這就意味着boost::scoped_ptr對象是不能作爲函數的返回值的(std::auto_ptr可以)。
–不能共享所有權
這點和std::auto_ptr類似。這個特點一方面使得該指針簡單易用。另一方面也造成了功能的薄弱——不能用於stl的容器中。
–不能用於管理數組對象
由於boost::scoped_ptr是通過delete來刪除所管理對象的,而數組對象必須通過deletep[]來刪除,因此boost::scoped_ptr是不能管理數組對象的,如果要管理數組對象需要使用boost::scoped_array類

shared_ptr

•缺點OR特性:
–1. shared_ptr是Boost庫所提供的一個智能指針的實現,shared_ptr就是爲了解決auto_ptr在對象所有權上的侷限性(auto_ptr是獨佔的),在使用引用計數的機制上提供了可以共享所有權的智能指針.
–2. shared_ptr比auto_ptr更安全
–3. shared_ptr是可以拷貝和賦值的,拷貝行爲也是等價的,並且可以被比較,這意味這它可被放入標準庫的一般容器(vector,list)和關聯容器中(map)。

weak_ptr

一種弱引用

3.強引用與弱引用

一個強引用當被引用的對象活着的話,這個引用也存在(就是說,當至少有一個強引用,那麼這個對象就不能被釋放)。boost::share_ptr就是強引用。

相對而言,弱引用當引用的對象活着的時候不一定存在。僅僅是當它存在的時候的一個引用。弱引用並不修改該對象的引用計數,這意味這弱引用它並不對對象的內存進行管理,在功能上類似於普通指針,然而一個比較大的區別是,弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存

當時舉的鴨子和雞的例子這裏就不說了


結果:

現在的結果就是我們上面看到那些已經封裝好的智能指針,至於爲什麼沒有放到結果這裏,是因爲我想它還會繼續發展,會越來越簡單方便。




發佈了41 篇原創文章 · 獲贊 19 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章