看板 CompBook 關於我們 聯絡資訊
Re: 網際網路四大服務 答客問 (1) - reference and delete 侯捷 [email protected] 2000.03.26 第一次發表於 清大.楓橋驛站(140.114.87.5).電腦書訊版(Computer/CompBook) 本文將於日後整理於 侯捷網站 侯捷網站:www.jjhou.com ---------------------------------------------------------------- 網際網路四大服務 答客問 (1) 之中,我代作者王家俊先生回覆了 sammy 讀者的一個問題。但我自己亦從中心生疑惑。所以我把 答客問 (1) 轉給家俊。雖然家俊正在服兵役,卻很快做了回覆。 以下是其來信。 jcwang wrote (2000/03/26) : > 侯大哥,您好: > > 感謝您替我對讀者 sammy 做了這麼詳盡的回答。我想讀者會選擇直接 > 問侯捷而不問作者,想必是因為「侯捷」這個招牌已經成了「有問 > 必答」的代名詞,寫信問原作者還未必能得到如此詳盡的回覆呢! :) > 這是一件可喜可賀的事! > > 我把一個比較接近我原意的程式寫在下方: > > // jcw 032600 > > #include <iostream.h> > > class A // 類似原來的 CWin32TCPEnv > { > public: > int val ; > } ; > > class B // 類似原來的 ClientStub > { > public: > B( A &initval ) : val( initval ) {} > A &val ; // 請注意,這裡是 by reference,這是關鍵! > } ; > > int main( void ) > { > A myA ; > myA.val = 5 ; > > B *pB = new B( myA ) ; // B 將 myA 關聯到它的 val 成員 > > A &finalA = pB->val ; // 類似 CWin32TCPEnv &Env = pStub->m_Env ; > > cout << finalA.val << endl ; // 輸出 5 > > delete pB ; > > cout << finalA.val << endl ; // 仍舊輸出 5。顯然 delete pB > // 並未自動解構參照成員 > > return 0 ; > // 這個時候 A 才被解構。替 A 寫一個 destructor 便可知。 > } > > 所以觀察到的是 desructor 並不會呼叫其「參照成員」的 destructor > (但確定會解構其實體成員)。從我手中的資料無法判斷這是 VC++ 的 bug > 還是標準行為,不過 HelloTCPIP 能正常工作完全依據的就是這一點。 > > 請侯大哥指教。 侯捷補充: > 我想讀者會選擇直接問侯捷而不問作者,想必是因為「侯捷」這個招牌 > 已經成了「有問必答」的代名詞。 不不不。侯捷絕非「有問必答」。除了對侯捷所著所譯之書籍提出 疑惑或指正,我會比較積極回答之外,其他則要看時間、看心情、 看主題、看能力。這陣子我整理信件加以回覆,竟然有「上個世紀」 寫來的讀者來函,說實在有點不好意思。對方可能早就犯嘀咕了吧。 回到正題。上封信中,我沒有仔細看書中 ClientStub's data member Env 的宣告方式,想當然爾地以 by value 方式來宣告它: class CClientStub { ... CWin32TCPEnv m_Env; }; 書中其實是以 by reference 方式來宣告它:(p45, #0076) class CClientStub { ... CWin32TCPEnv &m_Env; // <-- a reference member }; 換句話說 CClientStub 有一個 reference member。這便是關鍵所在。 我把書中程式重新簡化如下: #include <iostream> using namespace std; class CWin32TCPEnv { public: // default ctor CWin32TCPEnv() : m_i1(9), m_i2(28) { cout << "CWin32TCPEnv default ctor \n"; } // copy ctor CWin32TCPEnv(const CWin32TCPEnv& Env) : m_i1(Env.m_i1), m_i2(Env.m_i2) { cout << "CWin32TCPEnv copy ctor \n"; } // dtor ~CWin32TCPEnv() { cout << "CWin32TCPEnv dtor \n"; } void show() { cout << m_i1 << ' ' << m_i2 << endl; } private: int m_i1, m_i2; }; class CClientStub { public: // ctor CClientStub(CWin32TCPEnv& Env) : m_Env(Env) // (c) { cout << "CClientStub ctor \n"; } // dtor ~CClientStub() { cout << "CClientStub dtor \n"; } // public data member CWin32TCPEnv &m_Env; // 注意,是個 reference member. }; void BasicServiceThread(void* lpArg) { CClientStub *pStub = (CClientStub *)lpArg; CWin32TCPEnv &Env = pStub->m_Env; delete pStub; // CClientStub dtor (a) // 這裡測試 Env 還在否? Env.show(); // 9 28。資料仍在,顯示 delete operator 並不會解構 // 「物件內之 reference member」所代表的物件。 // scope 結束前,並未喚起 Env 的 CWin32TCPEnv dtor // 因為此處 Env 是個 reference。 (b) } int main() { CWin32TCPEnv Env; // CWin32TCPEnv default ctor Env.show(); // 9 28 CClientStub *pStub = new CClientStub(Env); // CClientStub ctor BasicServiceThread(pStub); // 這裡測試 Env 還在否? Env.show(); // 9 28 // scope 結束前,喚起 Env 的 CWin32TCPEnv dtor } 讓我們從 C++ 語言層面探討之。reference members 和 pointer members 的表現有以下兩個相同點: (1) 當物件被解構,其 reference members 或 pointer members 所代表 (所指向)的物件並不會被一併解構。見上例 (a) (2) pointer 或 reference 不會因為退出 scope 而使其 「所代表之物件」被自動解構。見上例 (b)。 (但可能因為其他原因而被解構) 此外,reference member 一定得於 member initialization list 中 初始化。見上例 (c)。參考《C++ Primer 中文版》p720. 針對上述 (1),Effective C++ 的 item6 給了一個忠告: item 6 : "Use delete on pointer members in destructors." 否則會出現 memory leak 問題。 家俊說: > 我手中的資料無法判斷這是 VC++ 的 bug 還是標準行為, > 不過 HelloTCPIP 能正常工作完全依據的就是這一點。 這是 C++ 標準行為。不過一般而言,reference 主要用於 函式的型式參數(formal parameters);一般程式較少使用 reference 獨立物件。 -- the end  -- ※ Origin: 楓橋驛站<bbs.cs.nthu.edu.tw> ◆ Mail: [email protected]