對于C/C++程序員來說, 內(nèi)存泄露是一個談之色變的話題, 很多時候, 機(jī)器運(yùn)行1天2天都是ok的, 但運(yùn)行到一個星期后, 就卡得要死。 實(shí)際上, 很多時候是內(nèi)存泄露造成的。 內(nèi)存泄露很容易引入, 但是定位起來非常非常難, 在內(nèi)存泄露初期, 通常沒有異常癥狀, 但隨著內(nèi)存泄露的累積, 內(nèi)存逐漸被啃光, 最終導(dǎo)致卡死或者死機(jī)。
申請堆內(nèi)存, 又沒有正確釋放, 就會導(dǎo)致內(nèi)存泄露, 聽起來好可怕啊, 那有沒有什么辦法可以解決這一難題呢? 有的! 人類的智慧還是很厲害的。 我么知道, 棧對象在離開其作用域的時候, 會自動調(diào)用析構(gòu)函數(shù), 所以, 可以考慮把某一棧對象與某一堆內(nèi)存綁定,且在其析構(gòu)函數(shù)中釋放堆內(nèi)存, 那么, 在該棧對象離開作用域時, 堆內(nèi)存自動釋放, 這就是智能指針(本質(zhì)是棧對象)的原理,簡直是妙招啊。 這個棧對象裝得像指針一樣, 所以我們稱之為智能指針, 其實(shí), 它不過就是個普通的棧對象而已。
在本文中, 我們來介紹智能指針中的一種------auto_ptr, 并從源碼的角度來看看auto_ptr使用中存在的一些常見問題:
我們先來看一段簡單的程序:
- #include <iostream>
- #include <memory> // 有auto_ptr這個類模板
- using namespace std;
-
- int main()
- {
- auto_ptr<int> p(new int(100));
-
- // p是個對象, 但重載了*運(yùn)算符, 所以我說它很能裝, 裝得像個指針
- cout << *p << endl; // 100
-
- return 0;
- }
C++是一門復(fù)雜的語言, 比這個更糟糕的是: 一些不合格的C++程序員正在用它。 我們看看上面的程序, 實(shí)際上, 編譯器做了太多的手腳, 這也是C++復(fù)雜的一個原因。 我們展開memory文件, 看看其中關(guān)于auto_ptr的代碼, 復(fù)制過來, 然后形成如下程序:
- #include <iostream>
- using namespace std;
-
-
- // 微軟能把代碼寫成這樣, 也是夠風(fēng)騷的
-
- // TEMPLATE CLASS auto_ptr
- template<class _Ty>
- class auto_ptr {
- public:
- typedef _Ty element_type;
- explicit auto_ptr(_Ty *_P = 0) _THROW0()
- : _Owns(_P != 0), _Ptr(_P) {}
- auto_ptr(const auto_ptr<_Ty>& _Y) _THROW0()
- : _Owns(_Y._Owns), _Ptr(_Y.release()) {}
- auto_ptr<_Ty>& operator=(const auto_ptr<_Ty>& _Y) _THROW0()
- {if (this != &_Y)
- {if (_Ptr != _Y.get())
- {if (_Owns)
- delete _Ptr;
- _Owns = _Y._Owns; }
- else if (_Y._Owns)
- _Owns = true;
- _Ptr = _Y.release(); }
- return (*this); }
- ~auto_ptr()
- {if (_Owns)
- delete _Ptr; }
- _Ty& operator*() const _THROW0()
- {return (*get()); }
- _Ty *operator->() const _THROW0()
- {return (get()); }
- _Ty *get() const _THROW0()
- {return (_Ptr); }
- _Ty *release() const _THROW0()
- {((auto_ptr<_Ty> *)this)->_Owns = false;
- return (_Ptr); }
- private:
- bool _Owns;
- _Ty *_Ptr;
- };
-
-
- int main()
- {
- auto_ptr<int> p(new int(100));
-
- // p是個對象, 但重載了*運(yùn)算符, 所以我說它很能裝, 裝得像個指針
- cout << *p << endl; // 100
-
- return 0;
- }
看了上面auto_ptr的源碼, 正在喝水的我, 差點(diǎn)嗆著了。 并想問: 微軟, 你還能再風(fēng)騷一點(diǎn)么
好吧, 我也懶得計(jì)較了。 于是, 對上面代碼進(jìn)行風(fēng)格整理, 并作出詳細(xì)的注釋, 就算是剖析一下auto_ptr的源碼吧:
- #include <iostream>
- using namespace std;
-
-
- // 簡單類
- class A
- {
- public:
- void fun()
- {
-
- }
- };
-
-
- template<class T>
-
- // 類模板
- class auto_ptr
- {
- public:
-
- // explicit構(gòu)造函數(shù), 禁止隱式轉(zhuǎn)化
- explicit auto_ptr(T *p = 0) throw()
- : m_bIsOwner(p != 0), m_ptr(p)
- {
- cout << "debug1" << endl;
- }
-
- // owner轉(zhuǎn)移
- auto_ptr(const auto_ptr<T>& y) throw()
- : m_bIsOwner(y.m_bIsOwner), m_ptr(y.release())
- {
- cout << "debug2" << endl;
- }
-
- // owner轉(zhuǎn)移
- auto_ptr<T>& operator=(const auto_ptr<T>& y) throw()
- {
- cout << "debug3" << endl;
-
- if (this != &y) // 當(dāng)前對象不是y對象
- {
- cout << "debug4" << endl;
-
- if (m_ptr != y.get()) // 當(dāng)前對象綁定的地址不是y對象綁定的地址
- {
- cout << "debug5" << endl;
-
- if (m_bIsOwner) // 如果當(dāng)前對象已經(jīng)綁定堆, 則要先釋放
- {
- cout << "debug6" << endl;
- delete m_ptr;
- }
-
- cout << "debug7" << endl;
-
- m_bIsOwner = y.m_bIsOwner; // 轉(zhuǎn)移owner
- }
- else if (y.m_bIsOwner) // 當(dāng)前對象與y綁定到同一塊堆上, 且y是owner, 則把y的owner轉(zhuǎn)移給當(dāng)前對象
- {
- cout << "debug8" << endl;
-
- m_bIsOwner = true;
- }
-
- cout << "debug9" << endl;
-
- m_ptr = y.release(); // 讓y不再是owner
- }
-
- cout << "debug10" << endl;
-
- return *this; // 返回當(dāng)前對象的引用
- }
-
- // 析構(gòu)函數(shù)
- ~auto_ptr()
- {
- cout << "debug11" << endl;
-
- if (m_bIsOwner) // 只有擁有owner屬性才釋放堆, 這樣避免重復(fù)釋放
- {
- cout << "debug12" << endl;
-
- delete m_ptr; // 即使m_ptr是空指針也木有關(guān)系
- }
- }
-
- // 重載對象的*運(yùn)算符, 使得對象"看起來"像指針, 可以執(zhí)行*p操作
- T& operator*() const throw()
- {
- cout << "debug13" << endl;
-
- return *get();
- }
-
- // 重載對象的->運(yùn)算符
- T *operator->() const throw()
- {
- cout << "debug14" << endl;
-
- return get();
- }
-
- // 獲得對象綁定的地址
- T *get() const throw()
- {
- cout << "debug15" << endl;
-
- return m_ptr;
- }
-
- // 去掉對象的owner屬性
- T *release() const throw()
- {
- cout << "debug16" << endl;
-
- ((auto_ptr<T> *)this)->m_bIsOwner = false;
- return m_ptr;
- }
-
- private:
- bool m_bIsOwner; // 對象是否擁有為owner的標(biāo)志
- T *m_ptr; // 對象綁定的指針
- };
-
-
- int main()
- {
- {
- cout << "------------------------------" << endl;
-
- // 用法錯誤, 因?yàn)闃?gòu)造函數(shù)中有explicit, 不允許類型轉(zhuǎn)化
- //auto_ptr<int> p = new int(10);
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- // ok
- auto_ptr<int> p(new int(10));
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- // 下面代碼有嚴(yán)重的運(yùn)行期錯誤, 實(shí)際上是嘗試delete棧上的內(nèi)容
- int a = 10;
- //auto_ptr<int> p(&a);
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p(new int(10));
-
- // 錯誤, p雖然"看似像"指針, 其本質(zhì)是對象, delete p;是未定義行為
- //delete p;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- int *q = new int(10);
- auto_ptr<int> p(q);
-
- // 錯誤, q釋放一次, p釋放一次, 重復(fù)釋放啊
- //delete q;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0;
-
- // 有debug3的打印, 但沒有debug4, 知道原因了吧
- p0 = p0;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0(new int(10));
-
- // 注意, 這是初始化, 不是復(fù)制, 所以不會有debug3的打印
- auto_ptr<int> p1 = p0;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0(new int(10));
- auto_ptr<int> p1;
-
- // 注意, 這才是賦值, 所有有debug3, debug4, debug5, debug7, debug9, debug10的打印
- // 為什么沒有debug6呢? 因?yàn)楫?dāng)前對象p1還不是owner
- p1 = p0;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0(new int(10));
- auto_ptr<int> p1(new int(20));
-
- // 有debug6的打印, 因?yàn)楫?dāng)先釋放p1綁定的對象, 否則內(nèi)存又泄露了啊
- p1 = p0;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0(new int(10));
-
- // 把owner轉(zhuǎn)給p1
- auto_ptr<int> p1(p0);
-
- // 終于見到你了, debug8
- p0 = p1;
- }
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p(new int(10));
-
- // 見到你了, debug13
- cout << *p << endl;
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<A> p(new A());
-
- // 終于見到你了, debug15
- p->fun();
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- auto_ptr<int> p0(new int(10));
- auto_ptr<int> p1(p0);
- auto_ptr<int> p2(p1);
-
- // 實(shí)際上, p3才是最后的winner, 才是最后的owner, 所以釋放堆的重任在p3身上
- auto_ptr<int> p3(p2);
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- // oh, my god, 內(nèi)存泄露, 本來要delete [] q; 現(xiàn)在析構(gòu)函數(shù)只執(zhí)行delete q;
- int *q = new int[3];
- auto_ptr<int> p(q);
- }
-
-
- {
- cout << "------------------------------" << endl;
-
- // oh, my god, 內(nèi)存泄露, 本來要delete [] q; 現(xiàn)在析構(gòu)函數(shù)只執(zhí)行delete q;
- int *q = new int[3];
- auto_ptr<int> p(q);
-
- // 已經(jīng)說過, 下面語句會造成內(nèi)存重復(fù)釋放
- //delete q;
- }
-
-
- // 最后說明一下, auto_ptr不適合做容器的元素, 這一點(diǎn)我們以后會再次討論到
-
-
- return 0;
- }
好了, 不多說auto_ptr了, 一切盡在代碼中。
|