• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            清風(fēng)竹林

            ぷ雪飄絳梅映殘紅
               ぷ花舞霜飛映蒼松
                 ----- Do more,suffer less

            Solmyr 的小品文系列之八:拷貝

            “zero 幫幫忙吧 ~~ ”

            “燦爛”的笑臉,充滿誠意的眼神,再加上點(diǎn)頭哈腰的姿勢,這三者構(gòu)成了一尊名為“有求于人”的塑像。

            在 QQ 上聊的正歡的 zero 抬起頭,看著塑像的作者和材料 ——— pisces ,方圓五十米內(nèi)唯一的女性程序員 ——— 問道:“什么事?”

            “我這里有一段 C++ 程序調(diào)不通。”

            “這類問題你應(yīng)該去問 Solmyr。”

            “哎呀,別開玩笑了,我哪敢去問他呀!總說我笨!上次問他一個(gè)小問題,結(jié)果又被訓(xùn)的狗血噴頭,哼!”,pisces 顯得忿忿不平,“還是你來幫幫我吧,我知道你是部門里有數(shù)的高手,肯定搞的定的。幫幫忙吧 ~~”

            zero 明顯的被打動了,于是,在 pisces 的努力下,zero 坐到了 pisces 的計(jì)算機(jī)前。

            “好吧,什么問題?”

            “是這樣的啦,這里有一組 C 風(fēng)格的 API ,負(fù)責(zé)管理設(shè)備上的字符通信鏈接。它們是好些年前設(shè)計(jì)的”,說著,pisces 調(diào)出了一些代碼:

            // old C style API
            typedef int conn_handle;
            typedef struct
            {
               /* ... 打開鏈接所需的參數(shù)和屬性 ... */
            }conn_attr;

            conn_handle open_conn(conn_attr* p_attr, char* buf, unsigned int buf_size);
            void close_conn(conn_handle h);

            char read_conn(conn_handle h);
            void write_conn(conn_handle h, char c);

            ...

            “枝節(jié)的東西不算,主干大概就是這樣,一對函數(shù)負(fù)責(zé)打開和關(guān)閉,一對函數(shù)負(fù)責(zé)讀寫。創(chuàng)建鏈接時(shí)候的那個(gè) buf 參數(shù)指向一個(gè)緩沖區(qū),這個(gè)要你自己分配并把長度傳進(jìn)去,和鏈接一一對應(yīng),read_conn/write_conn 會用它做緩沖。我的任務(wù)就是寫個(gè)類把這些 API 包裝起來。”,說著 pisces 又調(diào)出了另外一段代碼:

            // pisces' connection class
            class connection
            {
            private:
               conn_attr m_attr;
               bool m_opened;
               int m_bufsize;
               char* m_buf;

               conn_handle m_h;
               ...

            public:
               connection(const conn_attr& attr, int bufsize)
               {
                   m_attr = attr;
                   m_opened = false;
                   m_bufsize = bufsize;
                   m_buf = new char[m_bufsize];
               }
               ~connection() { delete m_buf; }

               void open()
               {
                   m_h = open_conn(&m_attr, m_buf, m_bufsize);
                   m_opened = true;
               }
               void close()
               {
                   close_conn(m_h);
                   m_opened = false;
               }

               char read()
               {
                   assert(m_opened);
                   return read_conn(m_h);
               }
               void write(char c)
               {
                   assert(m_opened);
                   write_conn(m_h, c);
               }
               ...

            };

            “應(yīng)該是很簡單的,可是不知道怎么回事,用了 connection 類的程序總是時(shí)不時(shí)的崩潰,說是非法的內(nèi)存操作。”,pisces 顯得很苦惱。

            zero 一眼就看出了毛病 ——— 這使他小小的自鳴得意了一下 ——— 但是表面上不動聲色,等到他看過 pisces 提供的“總是引發(fā)崩潰”的代碼段之后,他才開口說到:

            “這 是一個(gè)常見的錯(cuò)誤 pisces”,zero 盡量使自己的口吻和語氣聽起來象一個(gè)權(quán)威,“關(guān)于 C++,有一條重要的指導(dǎo)原則:析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)和賦值運(yùn)算符三者幾乎總是一起出現(xiàn)。也就是說,如果你為一個(gè)類寫了析構(gòu)函數(shù),那么往往你不得不再提 供一個(gè)拷貝構(gòu)造函數(shù)和一個(gè)賦值運(yùn)算符,違反它往往意味著錯(cuò)誤。你看這里:”

            說著,zero 在屏幕上標(biāo)出了兩行代碼:

            void some_func()
            {
               conn_attr attr;
               ...
               connection c1(512, attr);
               connection tmp = c1;
               ...
            }

            “這 里對象 tmp 是從 c1 拷貝構(gòu)造而來的,而你沒有定義拷貝構(gòu)造函數(shù),這使得編譯器在這里自動進(jìn)行按位拷貝,而這使得 tmp 和 c1 的所有成員都相等,包括 m_buf 成員。這樣在函數(shù)返回時(shí),c1 析構(gòu)的時(shí)候 delete 了一遍 m_buf,在 tmp 析構(gòu)的時(shí)候又 delete 了一遍 ……”

            “哦!我明白了!” pisces 打斷了 zero ,“所以就出現(xiàn)一個(gè)非法內(nèi)存操作,對吧?哎呀,這一條以前在學(xué)校里寫 string 類的時(shí)候遇到過,我怎么會忘了呢?”

            “對,你只要寫一個(gè)拷貝構(gòu)造函數(shù)和一個(gè)賦值運(yùn)算符處理一下 m_buf 指針就可以解決這個(gè)問題了。這你自己搞的定吧?”

            “我可以的,多謝了 zero !”

            zero 心滿意足的回到了自己的座位上,開始繼續(xù)和“你不懂我纖細(xì)的心”在 QQ 上探討“愛情的意義”。可是好景不長,沒過多久,本文開頭所描述的景象再一次的出現(xiàn)了。

            “zero 幫幫忙吧 ~~ ”

            zero 在心中嘆了口氣,抬頭問道:“又是什么問題,pisces?”

            “呃,還是那個(gè)類。我照你說的給 conn 添加了拷貝構(gòu)造函數(shù),非法內(nèi)存操作確實(shí)少多了,可還是有,還有好像鏈接傳輸數(shù)據(jù)也有點(diǎn)問題 ———— 你還是過來幫我看看吧 ~~”

            zero 心不甘情不愿的再次來到了 pisces 的計(jì)算機(jī)前,翻出 pisces 寫的拷貝構(gòu)造函數(shù)檢查起來:

            connection(const connection& other)
            {
               m_attr = other.m_attr;
               m_bufsize = other.m_bufsize;
               m_buf = new char[m_bufsize];
               memcpy(m_buf, other.m_buf, m_bufsize);
               m_opened = other.m_opened;

               m_h = other.m_h;
            }

            zero 的眉頭皺了起來,這個(gè)拷貝構(gòu)造函數(shù)似乎應(yīng)該可以解決問題,顯然現(xiàn)在兩個(gè) m_buf 各自指向合法的內(nèi)存,不再存在兩次釋放的問題。那么問題出在哪兒呢?zero 陷入了沉思。不過僅僅多花了不到 1 分鐘,zero 就明白了過來。

            “哦!我明白了!見鬼,我怎么會沒注意到這個(gè)。pisces ,問題還是出在 m_buf 上面,因?yàn)殒溄雍途彌_區(qū)指針是一一對應(yīng)的,所以拷貝構(gòu)造函數(shù)里新分配的緩沖區(qū)根本不起作用。”

            pisces 眨了眨眼,表情略顯呆滯。

            “給你舉個(gè)例子吧。”zero 飛快的鍵入一段測試代碼:

            connection* pc = NULL;

            {
               conn_attr attr;
               connection c1(512, attr);
               c1.open();
               pc = new connection(c1);
            }

            pc->write('A');

            “c1 的構(gòu)造函數(shù)里調(diào)用 new 為它的 m_buf 成員分配內(nèi)存,緊接著在 open 函數(shù)里調(diào)用 open_conn 打開了一個(gè)鏈接,注意這里我們傳入 open_conn 的參數(shù)是 c1.m_buf ,所以這個(gè)鏈接對應(yīng)的緩沖區(qū)指針是 c1.m_buf 。然后我們執(zhí)行 pc = new connection(c1),新對象從 c1 拷貝構(gòu)造,所以 pc->m_h 和 c1.m_h 相等,也就是說這兩個(gè)對象保存的 m_h 標(biāo)識著同一個(gè)鏈接,對應(yīng)的緩沖區(qū)指針都是 c1.m_buf ———— ”

            zero 象 Solmyr 常做的那樣停了下來,但卻失望的看到 pisces 毫無反應(yīng),只好接著往下說:

            “所 以接下來的 pc->write 在調(diào)用 write_conn 時(shí)候,這個(gè) API 并不知道這是通過另外一個(gè)對象在調(diào)用它,它仍然試圖使用 c1.m_buf 作為緩沖區(qū),但這個(gè)時(shí)候 c1 已經(jīng)結(jié)束了它的生命周期,c1.m_buf 已經(jīng)被釋放了,所以,這是一個(gè)非法的內(nèi)存訪問。”

            pisces 舔了舔嘴唇:“ …… 那 …… 那么現(xiàn)在怎么辦?”

            zero 翻了個(gè)白眼 ——— 很明顯 pisces 根本沒明白是怎么一回事 ——— 開始考慮怎樣應(yīng)付眼前這個(gè)問題。

            “嗯, 看樣子,這里必須考慮多個(gè)對象共享一個(gè)指針的問題,嗯,為了保證這塊內(nèi)存被釋放 …… 恐怕 …… 恐怕得用上引用計(jì)數(shù)技術(shù)(請參見“小品文系列之五:垃圾收集”)才搞得定,要不要用 boost::shared_ptr 呢?”,zero 一邊想,一邊自言自語。突然間 ———

            “邏輯的混亂導(dǎo)致實(shí)現(xiàn)上的復(fù)雜,zero,這個(gè) connection 類千瘡百孔啊。”,Solmyr 的聲音毫無預(yù)兆的在背后響起。

            zero 在 0.01 秒內(nèi)控制住了拔腿飛奔的沖動,以盡可能放松的姿態(tài)緩緩的轉(zhuǎn)過身來。在他的面前是披著一貫優(yōu)雅偽裝的 Solmyr,一手端著果汁,一手牢牢的拽著仍在拼命掙扎試圖逃走的 pisces 。

            “啊 Solmyr ,我正想找你呢,這個(gè)問題稍許有點(diǎn)棘手。”

            “是嗎?那你的腿為什么在抖?”

            “嗯?沒有,有點(diǎn)冷而已 …… 啊 Solmyr ,你剛剛說什么來著?”

            “邏輯的混亂導(dǎo)致實(shí)現(xiàn)上的復(fù)雜,zero,這個(gè) connection 類千瘡百孔。”Solmyr 把 pisces 按在旁邊的座位上,接著說到:“你剛才發(fā)現(xiàn)的問題只是其中之一而已。看一下這個(gè):”

            void some_func()
            {
               conn_attr attr;
               ...
               connection c1(512, attr);
               c1.open();
               ...
               connection tmp = c1;
               c1.close();
               tmp.write('a');
               ...
            }

            “這會導(dǎo)致什么?”

            “ …… 試圖寫入一個(gè)已經(jīng)關(guān)閉了鏈接。”

            “還需要我給出多次打開一個(gè)鏈接,多次關(guān)閉一個(gè)鏈接,以及各種鏈接處于打開狀態(tài)但讀寫卻會引發(fā)斷言錯(cuò)誤的例子嗎?”

            “ …… 不用了。”

            “那你打算怎樣修復(fù)這些問題?要不要在每個(gè)對象里保存一個(gè)由它拷貝構(gòu)造而來的對象列表?或者你打算在文檔里寫‘以下 371 種方式使用該類會導(dǎo)致無法預(yù)知的錯(cuò)誤’?”

            “ …… ”

            Solmyr 重重的嘆了口氣:“你被 pisces 誤導(dǎo)了,zero,因?yàn)槟阒幌胫趺磶?pisces 解決問題,如果一開始就讓你來設(shè)計(jì)這個(gè)類,情況一定不會這么糟糕。”說著,Solmyr 狠狠的瞪了 pisces 一眼。“不要忘了,C++ 類不是簡單的把一堆成員變量和成員函數(shù)湊在一起,永遠(yuǎn)記得這個(gè)原則:C++ 中用類來表示概念。”

            zero 點(diǎn)了點(diǎn)頭。

            “我來問你,connection 這個(gè)類應(yīng)該表示什么概念?”

            “呃,應(yīng)該表示‘鏈接’這個(gè)概念。”

            “一個(gè) connection 類的對象應(yīng)該代表 ……”

            “應(yīng)該代表一個(gè)實(shí)際‘鏈接’。”

            “很好。那么你告訴我,你剛才努力想設(shè)計(jì)出的那個(gè)拷貝構(gòu)造函數(shù)要干什么?”

            “ …… 讓兩個(gè) connection 對象能夠表示同一鏈接。”

            “所以 ……”

            “ …… 所以 …… 嗯 …… 哦 …… ”zero 露出了恍然大悟的表情:“所以我實(shí)際上想做的是要表達(dá)這樣一個(gè)概念:如果一個(gè) connection 對象沒有被拷貝,它就表示一個(gè)獨(dú)立的鏈接,如果它被拷貝了,那么它就和拷貝者表示同一個(gè)鏈接,這也包括拷貝者的拷貝者,拷貝者的拷貝者的拷貝者 …… 天哪,這根本是一團(tuán)亂麻!”

            “對,問題就在這里。一個(gè) connection 對象代表什么?你試圖給出一個(gè)在邏輯上非常混亂的答案,這導(dǎo)致了實(shí)現(xiàn)的復(fù)雜性。實(shí)際上,如果理清這個(gè)邏輯,問題是很簡單的:一個(gè) connection 對象代表一個(gè)鏈接,它構(gòu)造,代表建立了一個(gè)鏈接;它析構(gòu),代表這個(gè)鏈接走完了它的生命歷程 ——— 這里 open 和 close 這兩個(gè)成員函數(shù)根本就是多余的。至于拷貝構(gòu)造 ……”

            Solmyr 頓了頓,以一種斬釘截鐵式的語氣說到:

            “應(yīng)該禁止。”

            “禁止拷貝?!”

            “對, 應(yīng)該禁止。事實(shí)上,對于‘鏈接’這個(gè)概念而言,‘拷貝’動作含義模糊:拷貝意味著什么?拷貝構(gòu)造的對象所表示的鏈接和原來的鏈接是什么關(guān)系?當(dāng)使用 connection 類的程序員看到 connection c2 = c1; 這樣的代碼時(shí),他沒法從代碼本身看出這是什么意思,他會猜測,c1 和 c2 代表的是一個(gè)鏈接?還是兩個(gè)鏈接?只能通過查閱文檔來解決,這加重了使用者的負(fù)擔(dān),而如果禁止拷貝,所有智力正常的程序員都會明白每個(gè) connection 對象唯一的代表一個(gè)鏈接。”

            zero 若有所思的點(diǎn)了點(diǎn)頭。

            “同時(shí),這還能阻止程序員用傳值方式向函數(shù)傳遞 connection 對象 ——— 想象一下,如果一個(gè)程序員這樣使用 connection ,會發(fā)生什么?”,Solmyr 鍵入了下面的代碼:

            void send_a_greeting(connection c)
            {
               c.write("Hello!");
            }

            zero 沒費(fèi)什么勁就看出了問題:“函數(shù)的設(shè)計(jì)者以為他是在向調(diào)用者傳入的鏈接發(fā)送消息,但實(shí)際上這個(gè)函數(shù)在按值傳遞參數(shù)的時(shí)候創(chuàng)建了一個(gè)新鏈接。”

            Solmyr 點(diǎn)了點(diǎn)頭,繼續(xù)說到:“還有,從擴(kuò)展性的角度考慮,也應(yīng)該禁止拷貝。比如,假設(shè)你將來打算控制鏈接的創(chuàng)建,把創(chuàng)建過程封裝起來,那么這個(gè)拷貝構(gòu)造函數(shù)就在 你的封裝上捅了一個(gè)大窟窿 ——— 每個(gè)人都可以很方便的利用拷貝構(gòu)造任意創(chuàng)建鏈接;又比如,假設(shè)將來你需要支持多個(gè)類型的鏈接,要把 connection 作為一個(gè)類層次的接口基類,那時(shí),connection 的拷貝構(gòu)造就必須要禁止,而你之前支持拷貝構(gòu)造帶來的代價(jià)就是辛苦的翻遍之前所有的代碼去掉所有拷貝構(gòu)造。”

            “那,如果我確實(shí)需要在多處訪問一個(gè)鏈接,該 ……” zero 沒等 Solmyr 回答,自己就接了上去,“呃,也很簡單,只要傳遞引用就可以了,或者如果需要更好的控制,可以用智能指針什么的。”

            “完 全正確。說起來,其實(shí)許多類 ——— 比許多人所認(rèn)為的要多的多 ——— 所表示的概念對于‘拷貝’這個(gè)動作都沒有清楚的定義,比如常見的‘窗口’、‘文件’、‘事務(wù)’等等等等,禁止它們拷貝往往可以讓代碼的邏輯清楚許多。以后 你在設(shè)計(jì)類的時(shí)候,完全可以首先考慮是否禁止它的拷貝構(gòu)造,如果不能禁止,再去考慮怎么寫拷貝構(gòu)造函數(shù)的問題。好了 zero ,現(xiàn)在你能給出 connection 的實(shí)現(xiàn)嗎?”

            “Sure!只要將拷貝構(gòu)造函數(shù)和重載賦值運(yùn)算符設(shè)為私有,就可以禁止拷貝了。”zero 拖過鍵盤,三兩下屏幕上就出現(xiàn)了一個(gè)新的實(shí)現(xiàn):

            class connection
            {
            private:
               conn_attr m_attr;
               int m_bufsize;
               char* m_buf;

               conn_handle m_h;
               ...

            public:
               connection(const conn_attr& attr, int bufsize)
               : m_attr(attr), m_bufsize(bufsize)
               {
                   m_buf = new char[m_bufsize];
                   m_h = open_conn(&m_attr, m_buf, m_bufsize);
               }
               ~connection()
               {
                   close_conn(m_h);
                   delete m_buf;
               }

               void write(char c){ write_conn(m_h, c); }
               char read(){ return read_conn(m_h); }
               ...

            private:
               connection(const connection&);
               connection& operator=(const connection&);
            };

            “嗯,很好,這個(gè)問題可以告一段落了。”Solmyr 點(diǎn)了點(diǎn)頭,準(zhǔn)備離開,但又停了下來:“對了 zero ,pisces 他們這邊曾經(jīng)打報(bào)告要求增加人手,從今天的情況來看也確實(shí)需要有個(gè)懂點(diǎn) C++ 的人加強(qiáng)這邊。我看你正好有空,這個(gè)事就你來負(fù)責(zé)吧。”

            zero 心中暗暗叫苦,趕緊分辨:“沒有啊 Solmyr,我現(xiàn)在手邊的事情多得做不完啊!”

            “是嗎?哦 …… 對了,我剛才接到網(wǎng)管的報(bào)告,說有個(gè)人的電腦最近頻繁的訪問 QQ 的服務(wù)器,那個(gè)人是誰來著?”,Solmyr 又露出了他招牌式的微笑。

            “呃 …… 我又想了想,雖然我確實(shí)事情比較多,但團(tuán)隊(duì)合作精神還是要發(fā)揚(yáng)的。”

            “嗯,這樣就好。” Solmyr 心滿意足的離開了。

            “真見鬼!”確認(rèn) Solmyr 走遠(yuǎn)后,zero 才把在心里憋著的抱怨吐了出來:“好不容易有一段可以休息休息的空檔,這下子又泡湯了!真該死。”正在 zero 忿忿不平的時(shí)候,一個(gè)幽幽的聲音從旁邊飄了過來:

            “zero,剛才你和 Solmyr 講的什么‘概念’、‘禁止拷貝’、‘類層次’…… 這些都是什么呀?還是你給我講講吧 ~~”

            zero 轉(zhuǎn)過頭,看到 pisces 又在以非常“誠懇”的眼神看著他,再想到自己今后的任務(wù),突然間覺得腦袋隱隱的痛了起來 ——— 他似乎有一點(diǎn)明白了,為什么沒事的時(shí)候 Solmyr 總在揉自己的太陽穴 ……

            posted on 2009-08-19 11:38 李現(xiàn)民 閱讀(418) 評論(0)  編輯 收藏 引用 所屬分類: 絕對盜版

            久久影视综合亚洲| 久久亚洲精品中文字幕三区| 久久综合九色综合欧美就去吻| 青春久久| 久久免费美女视频| 香蕉久久夜色精品国产尤物| 久久久久国产精品熟女影院| AA级片免费看视频久久| 伊人久久大香线蕉亚洲五月天| 91精品国产色综久久| 久久强奷乱码老熟女网站| 久久成人影院精品777| 97精品伊人久久大香线蕉| 26uuu久久五月天| 日产精品久久久一区二区| 久久精品亚洲男人的天堂| 久久精品夜夜夜夜夜久久| 亚洲欧美久久久久9999 | 97香蕉久久夜色精品国产| 精品一区二区久久| 伊人久久大香线蕉av不变影院| 国产精品伊人久久伊人电影| 久久精品国产亚洲精品2020| 久久99热这里只有精品国产| 国产精品内射久久久久欢欢| 狠狠色丁香婷婷久久综合不卡| 人妻无码αv中文字幕久久琪琪布| 欧美国产精品久久高清| 欧洲性大片xxxxx久久久| 久久精品无码一区二区app| 久久99国产一区二区三区| 久久久久免费精品国产| 韩国三级大全久久网站| 97久久天天综合色天天综合色hd| 伊人久久大香线蕉av不卡| 色婷婷综合久久久中文字幕| 亚洲精品乱码久久久久久| 亚洲精品国产字幕久久不卡| 久久免费看黄a级毛片| 亚洲乱码精品久久久久..| 亚洲色婷婷综合久久|