程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++中的RAII機制詳解

C++中的RAII機制詳解

編輯:關於C++

C++中的RAII機制詳解。本站提示廣大學習愛好者:(C++中的RAII機制詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是C++中的RAII機制詳解正文


媒介

在寫C++設計形式——單例形式的時刻,在寫到實例燒毀時,設計的GC類是很奇妙的,而這一奇妙的設計就是依據當對象的性命周期停止時會主動挪用其析構函數的,而這一奇妙的設計也是有專業的名詞的——RAII。那以下將環繞RAII,周全的講授RAII的相干常識。

甚麼是RAII?

RAII是Resource Acquisition Is Initialization的簡稱,是C++說話的一種治理資本、防止洩露的習用法。應用的就是C++結構的對象終究會被燒毀的准繩。RAII的做法是應用一個對象,在其結構時獲得對應的資本,在對象性命期內掌握對資本的拜訪,使之一直堅持有用,最初在對象析構的時刻,釋放結構時獲得的資本。

為何要應用RAII?

下面說到RAII是用來治理資本、防止資本洩露的辦法。那末,用了這麼久了,也寫了這麼多法式了,行動上常常會說資本,那末資本是若何界說的?在盤算機體系中,資本是數目無限且對體系正常運轉具有必定感化的元素。好比:收集套接字、互斥鎖、文件句柄和內存等等,它們屬於體系資本。因為體系的資本是無限的,就比如天然界的石油,鐵礦一樣,不是取之不盡,用之不竭的,所以,我們在編程應用體系資本時,都必需遵守一個步調:

1.請求資本;
2.應用資本;
3.釋放資本。

第一步和第二步缺一弗成,由於資本必需要請求能力應用的,應用完成今後,必需要釋放,假如不釋放的話,就會形成資本洩露。

一個最簡略的例子:


#include <iostream>
 
using namespace std;
 
int main()
 
{
    int *testArray = new int [10];
    // Here, you can use the array
    delete [] testArray;
    testArray = NULL ;
    return 0;
}

我們應用new開拓的內存資本,假如我們不停止釋放的話,就會形成內存洩露。所以,在編程的時刻,new和delete操作老是婚配操作的。假如老是請求資本而不釋放資本,終究會招致資本全體被占用而沒有資本可用的場景。然則,在現實的編程中,我們老是會各類不當心的就把釋放操作忘了,就是編程的熟手在行,在幾千行代碼,幾萬行中代碼中,也會犯這類初級的毛病。

再來一個例子:

#include <iostream>
using namespace std;
 
bool OperationA();
bool OperationB();
 
int main()
{
    int *testArray = new int [10];
 
    // Here, you can use the array
    if (!OperationA())
    {
        // If the operation A failed, we should delete the memory
        delete [] testArray;
        testArray = NULL ;
        return 0;
    }
 
    if (!OperationB())
    {
        // If the operation A failed, we should delete the memory
        delete [] testArray;
        testArray = NULL ;
        return 0;
    }
 
    // All the operation succeed, delete the memory
    delete [] testArray;
    testArray = NULL ;
    return 0;
}
 
bool OperationA()
 
{
    // Do some operation, if the operate succeed, then return true, else return false
    return false ;
}
 
bool OperationB()
 
{
    // Do some operation, if the operate succeed, then return true, else return false
    return true ;
}

上述這個例子的模子,在現實中是常常應用的,我們不克不及等待每一個操作都是勝利前往的,所以,每個操作,我們須要做出斷定,上述例子中,當操作掉敗時,然後,釋放內存,前往法式。上述的代碼,極端癡肥,效力降低,更恐怖的是,法式的可懂得性和可保護性顯著下降了,當操作增多時,處置資本釋放的代碼就會愈來愈多,愈來愈亂。假如某一個操作產生了異常而招致釋放資本的語句沒有被挪用,怎樣辦?這個時刻,RAII機制便可以派上用處了。

若何應用RAII?

當我們在一個函數外部應用部分變量,當加入了這個部分變量的感化域時,這個變量也就別燒毀了;當這個變量是類對象時,這個時刻,就會主動挪用這個類的析構函數,而這一切都是主動產生的,不要法式員顯示的去挪用完成。這個也太好了,RAII就是如許去完成的。因為體系的資本不具有主動釋放的功效,而C++中的類具有主動挪用析構函數的功效。假如把資本用類停止封裝起來,對資本操作都封裝在類的外部,在析構函數中停止釋放資本。當界說的部分變量的性命停止時,它的析構函數就會主動的被挪用,如斯,就不消法式員顯示的去挪用釋放資本的操作了。如今,我們就用RAII機制來完成下面的例子。代碼以下:


#include <iostream>
using namespace std;
 
class ArrayOperation
{
public :
    ArrayOperation()
    {
        m_Array = new int [10];
    }
 
    void InitArray()
    {
        for (int i = 0; i < 10; ++i)
        {
            *(m_Array + i) = i;
        }
    }
 
    void ShowArray()
    {
        for (int i = 0; i <10; ++i)
        {
            cout<<m_Array[i]<<endl;
        }
    }
 
    ~ArrayOperation()
    {
        cout<< "~ArrayOperation is called" <<endl;
        if (m_Array != NULL )
        {
            delete[] m_Array;  // 異常感激益可達異常鋒利的review,具體可以加入益可達在本文的評論 2014.04.13
            m_Array = NULL ;
        }
    }
 
private :
    int *m_Array;
};
 
bool OperationA();
bool OperationB();
 
int main()
{
    ArrayOperation arrayOp;
    arrayOp.InitArray();
    arrayOp.ShowArray();
    return 0;
}

下面這個例子沒有多年夜的現實意義,只是為了解釋RAII的機制成績。上面說一個具有現實意義的例子:


/*
** FileName     : RAII
** Author       : Jelly Young
** Date         : 2013/11/24
** Description  : More information, please go to http://www.jb51.net
*/
 
#include <iostream>
#include <windows.h>
#include <process.h>
 
using namespace std;
 
CRITICAL_SECTION cs;
int gGlobal = 0;
 
class MyLock
{
public:
    MyLock()
    {
        EnterCriticalSection(&cs);
    }
 
    ~MyLock()
    {
        LeaveCriticalSection(&cs);
    }
 
private:
    MyLock( const MyLock &);
    MyLock operator =(const MyLock &);
};
 
void DoComplex(MyLock &lock ) // 異常感激益可達鋒利的review 2014.04.13
{
}
 
unsigned int __stdcall ThreadFun(PVOID pv)
{
    MyLock lock;
    int *para = (int *) pv;
 
    // I need the lock to do some complex thing
    DoComplex(lock);
 
    for (int i = 0; i < 10; ++i)
    {
        ++gGlobal;
        cout<< "Thread " <<*para<<endl;
        cout<<gGlobal<<endl;
    }
    return 0;
}
 
int main()
{
    InitializeCriticalSection(&cs);
 
    int thread1, thread2;
    thread1 = 1;
    thread2 = 2;
 
    HANDLE handle[2];
    handle[0] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread1, 0, NULL );
    handle[1] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void *)&thread2, 0, NULL );
    WaitForMultipleObjects(2, handle, TRUE , INFINITE );
    return 0;
}

這個例子可以說是現實項目標一個模子,當多個過程拜訪臨界變量時,為了不湧現毛病的情形,須要對臨界變量停止加鎖;下面的例子就是應用的Windows的臨界區域完成的加鎖。然則,在應用CRITICAL_SECTION時,EnterCriticalSection和LeaveCriticalSection必需成對應用,許多時刻,常常會忘了挪用LeaveCriticalSection,此時就會產生逝世鎖的景象。當我將對CRITICAL_SECTION的拜訪封裝到MyLock類中時,以後,我只須要界說一個MyLock變量,而不用手動的去顯示挪用LeaveCriticalSection函數。

上述的兩個例子都是RAII機制的運用,懂得了下面的例子,就應當能懂得了RAII機制的應用了。

應用RAII的圈套

在應用RAII時,有些成績是須要特殊留意的。容我漸漸道來。

先舉個例子:


#include <iostream>
#include <windows.h>
#include <process.h>
 
using namespace std;
 
CRITICAL_SECTION cs;
int gGlobal = 0;
 
class MyLock
{
public:
    MyLock()
    {
        EnterCriticalSection(&cs);
    }
 
    ~MyLock()
    {
        LeaveCriticalSection(&cs);
    }
 
private:
    //MyLock(const MyLock &);
    MyLock operator =(const MyLock &);
};
 
void DoComplex(MyLock lock)
{
}
 
unsigned int __stdcall ThreadFun(PVOID pv) 
{
    MyLock lock;
    int *para = (int *) pv;
 
    // I need the lock to do some complex thing
    DoComplex(lock);
 
    for (int i = 0; i < 10; ++i)
    {
        ++gGlobal;
        cout<< "Thread " <<*para<<endl;
        cout<<gGlobal<<endl;
    }
    return 0;
}
 
int main()
{
    InitializeCriticalSection(&cs);
 
    int thread1, thread2;
    thread1 = 1;
    thread2 = 2;
 
    HANDLE handle[2];
    handle[0] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void*)&thread1, 0, NULL );
    handle[1] = ( HANDLE )_beginthreadex(NULL , 0, ThreadFun, ( void*)&thread2, 0, NULL );
    WaitForMultipleObjects(2, handle, TRUE , INFINITE );
    return 0;
}

這個例子是在上個例子上的基本長進行修正的。添加了一個DoComplex函數,在線程中挪用該函數,該函數很通俗,然則,該函數的參數就是我們封裝的類。你運轉該代碼,就會發明,參加了該函數,對gGlobal全局變量的拜訪全部就亂了。你有麼有想過,這是為何呢?網上許多講RAII的文章,都只是說了這個成績,然則沒有說為何,在這裡,我好好的剖析一下這裡。

因為DoComplex函數的參數應用的傳值,此時就會產生值的復制,會挪用類的復制結構函數,生成一個暫時的對象,因為MyLock沒有完成復制結構函數,所以就是應用的默許復制結構函數,然後在DoComplex中應用這個暫時變量。當挪用完成今後,這個暫時變量的析構函數就會被挪用,因為在析構函數中挪用了LeaveCriticalSection,招致了提早分開了CRITICAL_SECTION,從而形成對gGlobal變量拜訪抵觸成績,假如在MyLock類中添加以下代碼,法式就又能准確運轉:

MyLock( const MyLock & temp )
{
    EnterCriticalSection(&cs);
}

這是由於CRITICAL_SECTION 許可屢次EnterCriticalSection,然則,LeaveCriticalSection必需和EnterCriticalSection婚配能力不湧現逝世鎖的景象。

為了不失落進了這個圈套,同時斟酌到封裝的是資本,因為資本許多時刻是不具有拷貝語義的,所以,在現實完成進程中,MyLock類應當以下:


class MyLock
{
public:
    MyLock()
    {
        EnterCriticalSection(&cs);
    }
 
    ~MyLock()
    {
        LeaveCriticalSection(&cs);
    }
 
private:
    MyLock(const MyLock &);
    MyLock operator =(const MyLock &);
};

如許就避免了面前的資本復制進程,讓資本的一切操作都在本身的掌握傍邊。假如要曉得復制結構函數和賦值操作符的挪用,可以好好的浏覽一下《深度摸索C++對象模子這本書》。

總結

說了這麼多了,RAII的實質內容是用對象代表資本,把治理資本的義務轉化為治理對象的義務,將資本的獲得和釋放與對象的結構和析構對應起來,從而確保在對象的生計期內資本一直有用,對象燒毀時資本必定會被釋放。說白了,就是具有了對象,就具有了資本,對象在,資本則在。所以,RAII機制是停止資本治理的無力兵器,C++法式員依附RAII寫出的代碼不只簡練優雅,並且做到了異常平安。在今後的編程現實中,可使用RAII機制,讓本身的代碼更英俊。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved