這幾天在研究Todolist源碼,看到一簡短的類,很很有意思,現把源碼列下,並附上簡短說明:
// macro to help prevent re-entrancy in functions<br />//定義一個宏AF_NOREENTRANT,防止函數中可重新進入</p><p>#define AF_NOREENTRANT /<br /> static BOOL bAFInHere = FALSE; /<br /> if (bAFInHere) /<br /> return; /<br />CAutoFlag af(bAFInHere, TRUE);</p><p>class CAutoFlag<br />{<br />public:<br /> CAutoFlag(BOOL& bFlag, BOOL bState); //bFlag為引用參數<br /> virtual ~CAutoFlag();</p><p>protected:<br /> BOOL& m_bFlag; //引用內部成員<br /> BOOL m_bInitialState;</p><p>};<br />
//////////////////////////////////////////////////////////////////////</p><p>// 構造時m_bFlag初始化為bFlag的引用,設定m_bFlag(bFlag)的值為bState,</p><p>//////////////////////////////////////////////////////////////////////</p><p>CAutoFlag::CAutoFlag(BOOL& bFlag, BOOL bState) : m_bFlag(bFlag)</p><p>{</p><p>m_bInitialState = bFlag;</p><p>m_bFlag = bState; //此時m_bFlag初始化為bFlag的引用</p><p>}</p><p>CAutoFlag::~CAutoFlag()</p><p>{</p><p>m_bFlag = m_bInitialState; //析構時bFlag恢複原值。</p><p>}</p><p>下面我們看一下函數及宏的具體應用:</p><p>void CToDoListWnd::OnTimerAutoSave()</p><p>{</p><p>AF_NOREENTRANT // 防止OnTimerAutoSave同時被多個地方調用。</p><p>// don't save if the user is editing a task label</p><p>if (!GetToDoCtrl().IsTreeLabelEditing())</p><p>SaveAll(FALSE, FALSE, FALSE, FALSE);</p><p>}</p><p>int CCheckComboBox::GetCurSel() const</p><p>{</p><p>CAutoFlag af(m_bDrawing, FALSE); //把m_bDrawing設為FALSE,函數執行完後自動回復原值。</p><p>return CComboBox::GetCurSel();</p><p>}</p><p>
註:
重入即表示重複進入,首先它意味著這個函數可以被中斷,其次意味著它除了使用自己棧上的變數以外不依賴於任何環境(包括static),這樣的函數就是purecode(純程式碼)可重新進入,可以允許有該函數的多個副本在運行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全域變數(包括static),一定要注意實施互斥手段。可重新進入函數在並行運行環境中非常重要,但是一般要為訪問全域變數付出一些效能代價。
在多線程(或多進程)程式設計中,“重入”是指多個線程或進程“同時”訪問某個程式碼片段(通常指某一函數)。因此,“可重新進入”就是指這個程式碼片段支援這樣的操作而不會產生任何問題。無狀態的程式碼片段肯定是可重新進入的,比如求兩個數的平方的函數,但如果程式碼片段共用某些資源(比如共用變數),則要達到可重新進入,必須使用一些同步和或互斥機制。
在單任務的系統中同樣有函數重入的問題,因為c/c++函數支援中斷和回調,為此編譯器在函數調用時將傳回值的地址做為一個參數壓棧;這就意味這,在你的函數執行的任何時候中斷都有可能發生,所以你在寫代碼的時候要加以小心,尤其是在設計中斷服務程式的時候。如果你想支援重入,最好寫成純程式碼(與任何全域變數無關),不得已用到全域變數,要對臨界區加鎖;如果你不想重入就關中斷,並保證你的函數是唯一執行個體運行。