天天看點

動态配置設定資源的自動釋放 – auto_ptr的實作原理

轉自http://patmusing.blog.163.com/blog/static/13583496020101824142699/

動态配置設定資源的自動釋放的英文是 Resource Allocation In Initialization,通常縮寫成RAII。

根據《C++ Primer》第4版:

During stack unwinding, the function containing the throw, and possibly other functions in the call chain, are exited prematurely. In general, these functions will have created local objects that ordinarily would be destroyed when the function exited. When a function is exited due to an exception, the compiler guarantees that the local objects are properly destroyed. As each function exits, its local storage is freed. … If the local object is of class type, the destructor for this object is called automatically. As usual, the compiler does not work to destroy an object of built-in type.

假定有一個類定義如下:

// 資源類

class Student

{

public:

         Student(const string name = "Andrew", string gender = "M", int age = 6)

         {

                   this->name = name;

                   this->gender = gender;

                   this->age = age;

         }

         void printStudentInfo()

         {

                   cout << "Name: " << name << " ";

                   cout << "Gender: " << gender << " ";

                   cout << "Age: " << age << endl;

                   //throw runtime_error("Throw an exception deliberately...");                          // (1)

         }

         ~Student()

         {

                   // 下面這條語句用來提示資源是否被釋放了

                   cout << "The destructor of class Student is called" << endl;

         }

private:

         string name;

         string gender;

         int age;

};

那麼類似下面代碼中的方式使用Student這個類,就會造成記憶體洩漏:

// 測試代碼

int main()

{

         Student *stu = new Student("Andrew", "M", 6);

         stu->printStudentInfo();

         return 0;

}

因為在return之前,沒有delete掉stu這個動态配置設定而來的資源(如果僅僅是這個程式,也沒有記憶體洩漏之憂,因為整個應用都結束運作了,在此隻是為了友善說明類似的使用方式是不可以的,即用了new動态配置設定了資源,而沒有對應的delete去回收)。為了防止這樣的方式造成的記憶體洩漏,上面的測試代碼應該增加一行delete stu:

// 測試代碼

int main()

{

         Student *stu = new Student("Andrew", "M", 6);                               // (2)

         stu->printStudentInfo();

delete stu;                                                                                            // (3)

         return 0;

}

這樣就不會造成記憶體洩漏了。運作該應用,輸出的結果是:

Name: Andrew Gender: M Age: 6

The destructor of class Student is called

輸出的The destructor of class Student is called表明了Student類的析構函數得到了調用。

現在,如果在(2)和(3)中間發生了異常,即将Student類中的(1)語句uncomment掉,就會出現下面這樣的錯誤提示:

This application has requested the Runtime to terminate it in an unusual way. Please contact the application’s support team for more information.

這樣一來語句(3)即delete stu;就不會得到執行,是以也會造成記憶體洩漏。為了消除上面的錯誤,我們在前面的測試代碼中,增加try-ctach來捕獲異常,代碼如下:

// 測試代碼

int main()

{

         Student *stu = new Student("Andrew", "M", 6);                              // (2)

         try

         {

                   stu->printStudentInfo();                                                            

         }

         catch(const exception &e)

         {

                   cout << e.what() << endl;

         }

         delete stu;                                                                                                      // (3)

         return 0;

}

輸出結果:

Name: Andrew Gender: M Age: 6

Throw an exception deliberately…

The destructor of class Student is called

這就說明,如果增加了try-catch,後面的delete stu;就會将用new動态配置設定的資源正确的予以釋放。

進一步地,如果在stu->printStudentInfo();中出現了異常,而且後面也沒有delete stu;這樣的語句,用new配置設定給stu的資源進行自動釋放?辦法是有的。為此,我們需要增加一個類,定義如下:

// 資源管理類

template<typename T>

class Resource

{

public:

         Resource(T* p)

         {

                   // 将新配置設定的到的資源的位址賦給res,現在res指向了新配置設定到的資源

                   res = p;

         }

         ~Resource()

         {

                   if(res)

                   {

                            // 如果res指向的位址中,資源還沒有被釋放,那麼則釋放之

                            delete res;

                            res = 0;

                            cout << "Resources are deallocated here." << endl;

                   }

         }

private:

         T *res;

};

把測試代碼改為:

// 測試代碼

int main()

{

         Student *stu = new Student("Andrew", "M", 6);                     // (2)

         // 将stu綁定到Resource<Student>類型的res變量,res是一個local對象,當程式超出其可見範圍時

         // 其析構函數會自動執行。而它的析構函數中,又會自動釋放stu所指向的資源

         Resource<Student> res(stu);

         try

         {

                   stu->printStudentInfo();                                                            

         }

         catch(const runtime_error &e)

         {

                   cout << e.what() << endl;

         }

         return 0;

}

上面代碼的運作結果:

Name: Andrew Gender: M Age: 6

Throw an exception deliberately…

The destructor of class Student is called

Resources are de-allocated here.

這說明即使沒有delete stu;程式也正确地析構了相關動态非配的資源,進而實作了動态配置設定資源的自動釋放。

在上面的代碼中,我們用stu->printStudentInfo();來調用相關的函數,如果想用res直接調用,則需要重載Resource類的->操作符,代碼如下:

// 資源管理類

template<typename T>

class Resource

{

public:

         Resource(T* p)

         {

                   // 将新配置設定的到的資源的位址賦給res,現在res指向了新配置設定到的資源

                   res = p;

         }

         ~Resource()

         {

                   if(res)

                   {

                            // 如果res指向的位址中,資源還沒有被釋放,那麼則釋放之

                            delete res;

                            res = 0;

                            cout << "Resources are deallocated here." << endl;

                   }

         }

         T* operator->() const

         {

                   if(res)

                            return res;

                   else

                            cout << "The underlying object is empty." << endl;

         }

private:

         T *res;

};

粗體字部分重載了->操作符,用來傳回該類中的私有成員變量res。

相應的,測試代碼做如下修改:

// 測試代碼

int main()

{

         Student *stu = new Student("Andrew", "M", 6);                     // (2)

         // 将stu綁定到Resource<Student>類型的res變量,res是一個local對象,當程式超出其可見範圍時

         // 其析構函數會自動執行。而它的析構函數中,又會自動釋放stu所指向的資源

         Resource<Student> res(stu);

         try

         {

                   //stu->printStudentInfo();            // 這行被注釋掉

                   res->printStudentInfo();            // 改用res來調用printStudentInfo

         }

         catch(const runtime_error &e)

         {

                   cout << e.what() << endl;

         }

         return 0;

}

運作結果和前面是一緻的,也實作了動态配置設定資源的自動釋放。事實上,這就是大名鼎鼎的auto_ptr的實作原理。

注意,代碼開始處需要包含以下語句:

#include <iostream>

#include <string>

#include <stdexcept>                 // 處理異常時,必須包含此頭檔案

using namespace std;

繼續閱讀