通用工具Utilities(一):STL中auto_ptr的實現以及auto_ptr_ref的理解

來源:互聯網
上載者:User

auto_ptr原始碼,參考The C++ Standard Library

/* The following code example is taken from the book * "The C++ Standard Library - A Tutorial and Reference" * by Nicolai M. Josuttis, Addison-Wesley, 1999 * * (C) Copyright Nicolai M. Josuttis 1999. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. *//* class auto_ptr * - improved standard conforming implementation */namespace std {    // auxiliary type to enable copies and assignments (now global)    template<class Y>    struct auto_ptr_ref {        Y* yp;        auto_ptr_ref (Y* rhs)         : yp(rhs) {        }    };    template<class T>    class auto_ptr {      private:        T* ap;    // refers to the actual owned object (if any)      public:        typedef T element_type;        // constructor        explicit auto_ptr (T* ptr = 0) throw()         : ap(ptr) {        }        // copy constructors (with implicit conversion)        // - note: nonconstant parameter        auto_ptr (auto_ptr& rhs) throw()         : ap(rhs.release()) {        }        template<class Y>        auto_ptr (auto_ptr<Y>& rhs) throw()         : ap(rhs.release()) {        }                // assignments (with implicit conversion)        // - note: nonconstant parameter        auto_ptr& operator= (auto_ptr& rhs) throw() {            reset(rhs.release());            return *this;        }        template<class Y>        auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {            reset(rhs.release());            return *this;        }                // destructor        ~auto_ptr() throw() {            delete ap;        }        // value access        T* get() const throw() {            return ap;        }        T& operator*() const throw() {            return *ap;        }        T* operator->() const throw() {            return ap;        }        // release ownership        T* release() throw() {            T* tmp(ap);            ap = 0;            return tmp;        }        // reset value        void reset (T* ptr=0) throw() {            if (ap != ptr) {                delete ap;                ap = ptr;            }        }        /* special conversions with auxiliary type to enable copies and assignments         */        auto_ptr(auto_ptr_ref<T> rhs) throw()         : ap(rhs.yp) {        }        auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() {  // new             reset(rhs.yp);             return *this;        }        template<class Y>        operator auto_ptr_ref<Y>() throw() {            return auto_ptr_ref<Y>(release());        }        template<class Y>        operator auto_ptr<Y>() throw() {            return auto_ptr<Y>(release());        }    };}

---------------------------------

auto_ptr_ref的理解如下:

auto_ptr是目前C++標準中唯一的一個智能指標(smart pointer),主要是用來自動管理指標所指向的記憶體資源。資源管理是程式設計中非常重要的一部分。資源(resource)是電腦中很寬泛的一個概念,用來表示程式中數量有限,用完就必須歸還的東西,比如常見的互斥鎖(mutex lock)、檔案指標、Win32中的畫刷(brush)……,其中記憶體(memory)是最常見的一種資源,也是我們最先接觸,使用最多的一種資源,因此它的地位至關重要。它的地位到底重要到什麼程度?對此有兩種截然不同的理念:

1.記憶體資源是如此的重要,以至於我們不能把它們交給電腦來管理,而必須由程式員來管理。

2.記憶體資源是如此的重要,以至於我們不能把它們交給程式員來管理,而必須由電腦來管理。

Java、C#、Eiffel秉承了第二種理念,把記憶體資源交給電腦管理,避免了程式員在記憶體管理上易犯的大量錯誤,從整體上提高了開發的效率,但是是以損失一定執行時間上的效率為代價的。因此這些語言在即時系統、作業系統、語言編譯器……等一些對時間要求比較嚴格的領域中運用的很少。

C語言秉承了第一種理念,C++也隨之繼承了這種理念,從而也就把記憶體資源管理交給了程式員,對於高段C程式員,當然是給了他們更多的靈活性,可以編製出非常精美的藝術品,但是對於初學者和不那麼高段的C程式員,記憶體管理卻是麻煩的根源,帶給他們更多的不是靈活性,而是揮之不去的連環噩夢。比如記憶體流失(memory leak)、野指標(wild pointer)等導致的一些極難察覺的bug,最後的調試除錯可能會讓我們覺得世界末日到了。【注1】

注1:不是有一種玩笑說法嗎?真正的程式員用C(或C++),我想,難度頗高的,由程式員本人負責的記憶體管理可能是支援這個觀點的一個重要理由。:)

但是在C++中有了另外一個管理記憶體(甚至資源)的選擇,那就是智能指標(smart pointer),資源擷取即初始化(resource acquisition is initialization)理念的具體實現。

【注2】

注2:在C++的准標準Boost庫中,引入了幾個新的智能指標scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr,相對於auto_ptr有它們的許多好處,感興趣的讀者可以到www.boost.org去看一看。Andrei Alexandrescu在他的Loki庫中也專門用Policy設計實現了一個可以擴充的SmartPtr模板,裡面用到的一些技術還是很有價值的,可以到http://sourceforge.net/projects/loki-lib/下載Loki庫閱讀。

標準中auto_ptr的引入主要就是為瞭解決記憶體資源管理這個讓人難以馴服的怪獸。不過在解決一個問題的同時,也會帶來一些新的問題,auto_ptr本身有擁有權(ownership)的轉移(transfer)問題,而且它本身不支援“值(value)語義”概念【注3】,因此不能用在STL容器裡面作為元素使用,在比較新的編譯器中,如果這樣使用的話,編譯器會阻止你。但是在稍微老一點的編譯器中使用的話,很可能會無風無浪的編譯通過,在執行的過程中,那麼我恭喜你,其時你正在一個很長的雷區裸奔,能夠毫髮無損通過的機率那就只有天知道了。:)

注3:關於這些概念,可以參考我寫的《範式的二維平面》。

auto_ptr本身的正確使用在很多書中有詳細的講解和樣本,而且我們也可以直接閱讀auto_ptr的原始碼獲得更直觀的感受。所以對於它的使用我不想再浪費時間和筆墨,在auto_ptr目前實現中【注4】,我們會看到一個奇怪的類模板auto_ptr_ref,第一次我在閱讀《The C++ Standard Library》的時候,看到講解auto_ptr的時候提到auto_ptr_ref就百思不得其解,說實話,這本書是寫得非常清晰易懂的,不過我覺得在auto_ptr這個地方花的筆墨比較吝嗇,沒有完全把問題講清楚。而且我看的很多書、文章上也並沒有詳細講解這個auto_ptr_ref問題,今天我想來對此深入探討一下,希望能夠拋磚引玉。

注4:auto_ptr的早期實現中有一些bug,後來Nicolai M. Josuttis 對此認真修正了,並作出了一個實現。讀者可以到http://www.josuttis.com/libbook/util/autoptr.hpp.html查看。讀者最好對照代碼看文章。其中關於成員函數模板,我並沒有講解,很多書上都有,主要是為瞭解決指標之間的轉化,特別是對於多態指標。

auto_ptr_ref就像它的名字一樣,把一個值轉化為引用(reference),具體的說,也就是把一個右值(rvalue)轉化為一個左值(lvalue)。【注5】我們可能會很奇怪,C++什麼時候還需要用到這種功能?很不幸,為了保證auto_ptr的正確行為和一定的安全性,需要用到這個功能。

注5:到底什麼是左值,什麼是右值?有許多讓人混淆,並不明晰的概念表述,我會在下一篇文章中表明我的觀點。

我們都知道,auto_ptr在進行複製操作(assignment operator and copy constructor)的時候,資源的擁有權(ownership)會發生轉移,也即原來的auto_ptr所指向的記憶體資源轉給了新的auto_ptr,本身已經為空白。所以說auto_ptr不支援“值語義”,即被複製的值不會改變。一個例子可能更能說明問題:

auto_ptr<int> ap1(new int(9));auto_ptr<int> ap2(ap1);// ap1失去擁有權,現在指向空,ap2現在獲得指9的//記憶體資源ap1 = ap2;//ap2失去擁有權,現在指向空,ap1現在獲得指向9的記憶體資源

我們在觀察auto_ptr的assignment operator、copy constructor的實現時,也能夠發現它的參數是auto_ptr& rhs,而不是auto_ptr const& rhs【注6】。也就是說,auto_ptr進行複製操作的時候,它的引數(argument)必須是一個可以改變的左值(事實是這個值必定被改變)。

注6:這種寫法你可能不習慣,其實就是const auto_ptr& rhs,我為什麼要用這種寫法,可以參考我寫的《C之詭譎(下)》,就知道我並不是為了標新立異。:)

我們最常見到的,複製操作的參數類型都是引用到常量(reference to const),這正好是為了避免改變傳進來的引數(argument)。由於不會改變被引用的值,所以C++標準規定:常量引用可以引用右值。比如下列代碼都是合法的:

int const& ri = 60;//60是右值list<int> const& rl = list<int>();//list<int>()是右值int fun(){…}; int const& rf = fun();//fun()是右值

但是一般引用(非常量引用)是絕對不可以引用右值的。比如下列代碼都是非法的:

int& ri = 60;list<int>& rl = list<int>();int fun(){…}; int& rf = fun();

在我們前面談到的auto_ptr,它的複製操作的參數類型恰好是非常量引用。所以對於下面的情況它就不能正確處理。

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));//case1 等號右邊的是一個臨時右值auto_ptr<int> fun(){//一個產生auto_ptr<int>的source函數
return auto_ptr<int>(new int(8));}auto_ptr<int> ap2 ( fun() );//case 2 調用fun()產生的auto_ptr<int>是右值

註:對於case 1,將調用auto_ptr& operator= (auto_ptr& rhs)或template<class Y> auto_ptr& operator= (auto_ptr<Y>& rhs)。但是,根據C++語言的規定,引用不能指向臨時對象,除非這個對象是常量。所以case 1是危險的 。。對於case 2,fun()返回一個對象,這個對象也時臨時對象,auto_ptr (auto_ptr& rhs) 或 template<class Y> auto_ptr (auto_ptr<Y>&
rhs) 這兩個建構函式參數類型都是引用,因此也是不安全的。

而這種情況不但合法,也是很常見的,我們不能拒絕這種用法,怎麼辦?天才的C++庫設計者已經為我們想到了這一點,auto_ptr_ref的引入就是為瞭解決這個問題。仔細觀察最下面的auto_ptr實現代碼,我們會看到這樣幾行:

/* special conversions with auxiliary type to enable copies and assignments */auto_ptr(auto_ptr_ref<T> rhs) throw(): ap(rhs.yp) {}auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() {// new   reset(rhs.yp);   return *this;}template<class Y>operator auto_ptr_ref<Y>() throw(){  return auto_ptr_ref<Y>(release());}

這就是關鍵所在了。對於一個auto_ptr右值,不可能為它調用正常的賦值運算子函數和複製建構函式,舉個例子說明,對於語句

auto_ptr<int> ap1 = auto_ptr<int>(new int(8));

首先產生臨時對象右值auto_ptr<int>(new int(8)),然後使用轉型函數模板

template<class Y> operator auto_ptr_ref<Y>() throw(){ return auto_ptr_ref<Y>(release());};

由auto_ptr<int>(new int(8)),首先調用成員函數release(),然後由擷取的原始指標產生另外一個臨時對象右值auto_ptr_ref<int>(一個指向動態儲存裝置區中8的指標)。這個時候我們再看一個建構函式

auto_ptr(auto_ptr_ref<T> rhs) throw(): ap(rhs.yp) { }

它的參數類型不是引用,採用的是傳值(by value),所以可以接受右值,這時候,調用auto_ptr_ref<int>預設產生的複製建構函式(copy constructor),用上面最後得到的那個auto_ptr_ref<int>臨時對象右值作為引數,產生了rhs,接著auto_ptr_ref<int>臨時對象右值自動析構結束生命,後面的ap(rhs.yp)完成最後的工作。

對這個用法感興趣的同仁,可以參考SGI的STL源碼

auto_ptr機制協助我們把右值轉化為左值。這一機制的理論基礎是“重載”和“template參數推導規則”之間一個細微的不同處。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.