1. 智能指針auto_ptr的引入
auto_ptr是C++標準庫中的智能指針模板類,頭文件<memory>
auto_ptr的出現,主要是爲了解決“有異常拋出時發生內存泄漏”的問題。如下的簡單代碼是這類問題的一個簡單示例。
int* p = new int(100);
try
{
doSomething();
cout << *p << endl;
delete p;
}
catch(exception& e)
{
}
當doSomething();部分拋出異常,將導致指針p所指向的空間得不到釋放而導致內存泄露。auto_ptr的引入解決了這類問題。
2. auto_ptr的源代碼(未可讀性進行了少許改動的源碼)
1 namespace std
2 {
3 template<class T>
4 class auto_ptr
5 {
6 private:
7 T* ap;
8 public:
9
10 // constructor & destructor ----------------------------------- (1)
11 explicit auto_ptr (T* ptr = 0) throw() : ap(ptr){}
12
13 ~auto_ptr() throw()
14 {
15 delete ap;
16 }
17
18
19 // Copy & assignment --------------------------------------------(2)
20 auto_ptr (auto_ptr& rhs) throw() :ap(rhs.release()) {}
21 template<class Y>
22 auto_ptr (auto_ptr<Y>& rhs) throw() : ap(rhs.release()) { }
23
24 auto_ptr& operator= (auto_ptr& rhs) throw()
25 {
26 reset(rhs.release());
27 return *this;
28 }
29 template<class Y>
30 auto_ptr& operator= (auto_ptr<Y>& rhs) throw()
31 {
32 reset(rhs.release());
33 return *this;
34 }
35
36 // Dereference----------------------------------------------------(3)
37 T& operator*() const throw()
38 {
39 return *ap;
40 }
41 T* operator->() const throw()
42 {
43 return ap;
44 }
45
46 // Helper functions------------------------------------------------(4)
47 // value access
48 T* get() const throw()
49 {
50 return ap;
51 }
52
53 // release ownership
54 T* release() throw()
55 {
56 T* tmp(ap);
57 ap = 0;
58 return tmp;
59 }
60
61 // reset value
62 void reset (T* ptr=0) throw()
63 {
64 if (ap != ptr)
65 {
66 delete ap;
67 ap = ptr;
68 }
69 }
70
71 // Special conversions-----------------------------------------------(5)
72 template<class Y>
73 struct auto_ptr_ref
74 {
75 Y* yp;
76 auto_ptr_ref (Y* rhs) : yp(rhs) {}
77 };
78
79 auto_ptr(auto_ptr_ref<T> rhs) throw() : ap(rhs.yp) { }
80 auto_ptr& operator= (auto_ptr_ref<T> rhs) throw()
81 {
82 reset(rhs.yp);
83 return *this;
84 }
85 template<class Y>
86 operator auto_ptr_ref<Y>() throw()
87 {
88 return auto_ptr_ref<Y>(release());
89 }
90 template<class Y>
91 operator auto_ptr<Y>() throw()
92 {
93 return auto_ptr<Y>(release());
94 }
95 };
96 }
3. auto_ptr的使用
3.1 創建auto_ptr對象
auto_ptr構造時取得某個對象的所有權,在析構時釋放該對象。我們實際上是創建一個auto_ptr<Type>類型的局部對象,該局部對象析構時,會將自身所擁有的指針空間釋放,所以不會有內存泄露。
auto_ptr<int> p(new int(1));//推薦
//或
int* np = new int(1);
auto_ptr<int> p(np);
創建auto_ptr對象時注意的幾個問題
(1) auto_ptr的構造函數爲explicit,阻止了一般指針隱式類型轉換爲auto_ptr的構造,所以如下的創建方式是編譯不過的。
int* p = new int(1);
auto_ptr<int> ap = p;
如下代碼詳細解釋了關於explicit的作用。
#include <iostream>
using namespace std;
class Test1
{
public:
Test1(int i):iValue(i){};
private:
int iValue;
char cValue;
};
class Test2
{
public:
explicit Test2(int i):iValue(i){};
private:
int iValue;
char cValue;
};
int main(int argc, char* argv[])
{
Test1 t1 = 1;//t1.iValue值爲1,cValue值爲char類型默認值
Test2 t2 = 2;//編譯不過, error: conversion from 'int' to non-scalar type 'Test2' requested
}
(2) 由於auto_ptr對象析構時會刪除它所擁有的指針,所以使用時避免多個auto_ptr對象管理同一個指針。如下的使用方法應該避免。
int* np = new int(1);
auto_ptr<int> p1(np);
auto_ptr<int> p2(np);
這樣使用會造成p1和p2在析構時都試圖刪除np,C++標準中多次刪除同一個對象會導致未定義的行爲。且當p1析構而p2仍然被使用時,會導致空指針訪問風險。
(3)auto_ptr的內部實現中,析構函數中刪除對象使用delete而不是delete[],所以auto_ptr不能用來管理數組指針。
int *p = new int[100];
auto_ptr<int> ap(p);
如上使用auto_ptr的方式,在ap析構時,執行delete,僅僅釋放了數組的第一個元素的空間,仍然會造成內存泄漏,所有使用auto_ptr管理數組不合理的。
(4)C++中對一個空指針NULL執行delete操作是安全的。所以在auto_ptr的析構函數中無須判斷它所擁有指針是否爲空。
3.2 auto_ptr的拷貝構造和賦值
auto_ptr要求對它所擁有的指針完全佔有,這一點與引用計數的智能指針不同,也就是說,一個一般指針不能同時被兩個auto_ptr所擁有,一方面使用者要避免將用同一個指針構造auto_ptr(3.1(2)的那種方式),另一方面auto_ptr在拷貝構造和賦值運算符重載時要做特殊處理,具體的做法是對所有權進行了完全轉移,在拷貝和賦值時,剝奪原auto_ptr對指針的擁有權,賦予當前auto_ptr對指針的擁有權,當前auto_ptr獲得auto_ptr的指針,並使原auto_ptr的指針置空,由於會修改原對象,所以auto_ptr的拷貝構造函數以及賦值運算符重重載函數的參數是引用而不是常(const)引用。
這部分需要注意的幾個問題
(1) auto_ptr對象被拷貝或者被賦值後,已經失去了對原指針的所有權,此時,對這個auto_ptr的讀取操作是不安全的。如下代碼是不安全的。
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2(p1);
cout << *p1 << endl;
//and
auto_ptr<int> p3=p1;
cout << *p1 << endl;
這種情況較爲隱蔽的情形出現在將auto_ptr作爲函數參數按值傳遞,因爲在函數調用過程中在函數的作用域中會產生一個局部的臨時auto_ptr對象來接收傳入的 auto_ptr(拷貝構造),這樣,傳入的實參auto_ptr的對其指針的所有權轉移到了臨時auto_ptr對象上,臨時auto_ptr在函數退出時析構,所以當函數調用結束,原實參所指向的對象已經被刪除了。
void func(auto_ptr<int> ap)
{
cout << *ap << endl;
}
auto_ptr<int> ap1(new int(1));
func(ap1);
cout << *ap1 << endl;//錯誤,函數調用結束後,ap1已經不再擁有任何對象了
因此要避免使用auto_ptr對象作爲函數參數按值傳遞,按引用傳遞在調用函數是不會發生所有權轉移,但是無法預測函數體內的操作,有可能在函數體內進行了所有權的轉移,因此按引用傳遞auto_ptr作爲函數參數也是不安全的。使用const 引用傳遞則可以阻止在函數體內對auto_ptr對象的所有權轉移。如果不得不使用auto_ptr對象作爲函數參數時,儘量使用const引用傳遞參數。
(2) auto_ptr支持所擁有的指針類型之間的隱式類型轉換。
class base{};
class derived: public base{};
//下列代碼就可以通過,實現從auto_ptr<derived>到auto_ptr<base>的隱式轉換,因爲derived*可以轉換成base*類型
auto_ptr<base> apbase = auto_ptr<derived>(new derived);
(3) C++的STL容器對於容器元素類型的要求是有值語義,即可以賦值和複製。auto_ptr在賦值和複製時都進行了特殊操作,所以auto_ptr對象不能作爲STL容器元素。
3.3 auto_ptr對象的提領操作。
可以像使用一般指針一樣,通過*和->運算符對auto_ptr所有用的指針進行提領操作。首先必須確保這個auto_ptr對象確實擁有某個指針,否則,這個操作的行爲即對空指針的提領是未定義的。
struct A
{
void f();
}
auto_ptr<A> apa(new A);
(*apa).f();
apa->f();
3.4 auto_ptr的輔助函數
(1) T* get(),獲得auto_ptr所擁有的指針。
(2) T* release(), 釋放auto_ptr的所有權,並將所有用指針返回。
(3) void reset(T* ptr=0), 接收所有權,接收之前擁有其它指針的話,必須先釋放其空間。