引入智能指針:
智能指針的實現原理:
資源分配即初始化RAII(Resource Acquisition Is Initialization):
定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配
和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋放。
實現機制:利用類的構造和析構函數(釋放資源)是由編譯器自動調用的。
智能指針:
1.管理指針執行對象的釋放問題。
2.可以像指針一樣使用。
在c++標準庫里主要有四個智能指針:
C++四種智能指針auto_ptr、unique_ptr、shared_ptr和weak_ptr。
其中auto_ptr是C++98標準化引入的;
scope_ptr、shared_ptr和weak_ptr是C++11標準化才引入的(當然,早在C++03的TR1中就已經可以使用了)。我們都知道,auto_ptr雖說簡單,但使用起來卻到處是坑,以至于大家都不提倡使用。
shared_ptr是引用計數的智能指針,被奉為裸指針的完美替身,因此被廣泛使用。也可能正是這個原因,scope_ptr 和 weak_ptr似乎被大家遺忘了(或許還會以為它們純屬多余)。
上述言論引自網上
下面簡單總結下這幾個智能指針:
下面簡單總結下這幾個智能指針:
1.auto_ptr管理權轉移
帶有缺陷的設計 ----->c++98/03
在任何情況下都不要使用;
在任何情況下都不要使用;
2.scoped_ptr(boost)
unique_ptr(c++11)
防拷貝--->簡單粗暴設計--->功能不全。
3、shared_ptr(boost/c++11)
引用計數--->功能強大(支持拷貝、支持定制刪除器)
缺陷---->循環引用(weak_ptr配合解決)。
這篇文章主要的內容是講解auto_ptr所引出的一系列問題:
一、auto_ptr智能指針
(無論在什么情況下都不要使用,下面來詳細介紹下為什么不要使用它,可能會有人問,既然不讓使用,那么為什么還要把它放在c++的標準庫里呢?
是的,一開始我也是有這個疑問的,--->其實是因為c++標準庫里的內容一經規定,就不允許修改了)
下面模擬實現其基本功能:
1.AutoPtr的第一種實現方法:
采用資源轉移的方法。
一個指針將一塊空間的權限完全交給了另一個指針。(權限的轉移)
模擬實現代碼如下:
賦值運算符存在問題:
(由此可知對于動態內存空間只能由一個智能指針來管理,從而避免下面的問題。
存在的問題:
問題①:這種將一塊空間的權限完全交給別人的方法:
產生問題的是拷貝構造和賦值運算符重載函數。
當調用了拷貝構造或是賦值運算符的重載函數任意一個時:
假設調用了拷貝構造函數,我們的本意是用一個對象去構造一個與之一模一樣的對象,可是結果呢,我們把自己給弄丟了,完全沒有達到我們預期的目標。
所以當我們在上述代碼中的Test1中調用了拷貝構造函數時,如果我們想要修改第一次構造出來的對象的內容時,會發現對象的資源已經被清理了,所以會導致程序崩潰。
問題②:先看如下示例:
問題③:
AutoPtr<int> ap(AutoPtr<int>(new int(1)))
//括號里是構造一個無名對象,無名對象,具有常性,和第②個問題一樣,在拷貝構造ap時,拷貝構造函數的參數應該由const來修飾,還有一個問題就是,在vs2010下,編譯器對其做了一些優化,本來這一條語句,應該是先調用構造函數構造匿名對象,然后由匿名對象去拷貝構造對象ap,但是由于在構造了匿名對象之后,又馬上去拷貝構造對象,而且是在一條語句執行的,所以編譯器對其進行了優化,只會調用構造函數,而不會去調用拷貝構造函數,將構造的無名對象值直接給了ap這個對象,所以在vs2010下程序并沒有崩潰,而在Linux下編譯時就會報出錯誤。
下面是我在Linux下測試時的代碼:
編譯錯誤:
總結:由上面的編譯錯誤我們可以得知,這種由一個常性對象去拷貝構造一個Auto_ptr的想法是不現實的,所以才有了第三種情況。
實際上,實現方法三就是為了解決用一個臨時右值對象去拷貝構造一個對象。
2.AutoPtr的第二種實現方法:
存在野指針的問題:
同樣用示例來分析這個問題:
會發生內存泄漏,因為ap4與ap5共同管理一塊空間,當ap5出了if語句的作用域后,已被析構,空間被回收,那么在下面在去訪問ap4就有了問題,ap4已然成為了一個野指針。
③類型轉化
c++標準庫里的的auto_ptr的實現:
但還有是有缺陷:訪問空指針的問題無法規避,也就是實現方法一存在的第一個問題
我先貼出標準庫里的源碼:
示例分析:
如果您還想知道標準庫里究竟是怎樣實現的,可以繼續往下看,希望可以對您有所幫助。
源碼分析:
為了模擬一個一般指針,所以重載以下兩個符號是為了使得智能指針可以像一般指針一樣使用。
智能指針內封裝的一個原生態指針:
二、ScopedPtr(獨占空間--->防拷貝、防賦值)
(其實在c++標準庫里稱為unique_ptr,很好理解了,因為AutoPtr就是因為轉移資源,以及轉交權限從而引發一系列的問題的,追根到底其實就是拷貝構造和賦值運算符重載函數導致的。所以這個智能指針就是防拷貝和賦值的)。
因為AutoPtr智能指針在拷貝構造和賦值運算符重載方面有些許問題,所以就有了ScopedPtr,既然拷貝構造和賦值運算符重載會引發一些問題,那么是不是可以不允許它拷貝和賦值呢?
既然拷貝和賦值容易出現問題,那么這種智能指針就不允許拷貝和賦值。
解決上述問題的方法:
實現這種機制的方法有三種:
1)將拷貝構造函數和賦值運算符重載函數的聲明寫成公有的,但是對這兩個函數不進行定義。
2)將拷貝構造函數和賦值運算符重載函數在類型直接實現,但是將它們的訪問限定符設定為私有的。
3)將拷貝構造函數和賦值運算符重載函數的聲明寫成私有的,但是對這兩個函數不進行定義。
分析上述三種方法:
第一種:如果將其聲明為公有的,那么如果有其他人在類外對你的拷貝構造和賦值運算符重載函數進行實現,那么還是沒有達到防拷貝、防賦值的要求。--->pass掉
第一種:如果將其聲明為公有的,那么如果有其他人在類外對你的拷貝構造和賦值運算符重載函數進行實現,那么還是沒有達到防拷貝、防賦值的要求。--->pass掉
第二種:個人覺得,既然都已經不允許拷貝和賦值了,為什么還要多此一舉的寫出其實現方法呢?所以呢----->pass掉
所以:就第三種方法覺得合適。
在這里我只貼出第三種方法的代碼:
scoped_array//管理數組
(boost庫里面的,c++標準庫由于已經有相似功能的vector,所以并未將其此加入)
總結:c++標準庫里的智能指針只能管理單個對象的動態空間,而不能讓其管理一段連續空間,例如:動態開辟的數組的空間。
總結:c++標準庫里的智能指針只能管理單個對象的動態空間,而不能讓其管理一段連續空間,例如:動態開辟的數組的空間。
auto_ptr無論什么時候都不要使用
scoped_ptr是對auto_ptr所存在的問題的一個解決。
所以一般面試時,沒有特別指定讓你實現哪個智能指針,最好寫出這個,因為這個最簡單呀,千萬不要寫出auto_ptr,這個智能指針存在缺陷!
雖然c++標準庫里面加入了auto_ptr智能指針,但是還是不要使用它,c++標準庫里仍然保留它,應該是為了向前兼容。
因為既然已經加入了標準庫,那么肯定會有程序員使用的,所以還是不能隨意廢棄掉。
后續我還會繼續總結關于shared_ptr的實現細節。
題外話:
因為剛開始接觸智能指針,自身能力不足,所以哪里寫的有問題的歡迎大家提出來。
共同進步!
每天進步一點點!