• <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>

            Shuffy

            不斷的學習,不斷的思考,才能不斷的進步.Let's do better together!
            posts - 102, comments - 43, trackbacks - 0, articles - 19
            【轉】http://www.shnenglu.com/tiandejian/archive/2007/09/23/ec_28.html

            第28條:     不要返回指向對象內部部件的“句柄”

            假設你正在設計一個與矩形相關的應用程序。每個矩形的區域都由它的左上角和右下角的坐標來表示。為了 Rectangle 對象盡可能的小巧,你可能會做出這樣的決定: Rectangle 自身并不保存這些點的坐標的信息,取而代之的是將這些信息保存在一個輔助結構中,然后讓 Rectangle 指向它:

            class Point {                   // 表示點的類

            public:

             Point(int x, int y);

             ...

             

             void setX(int newVal);

             void setY(int newVal);

             ...

            };

             

            struct RectData {               // Rectangle 類使用的點的數據

             Point ulhc;                    // ulhc = " 左上角點的坐標

             Point lrhc;                    // lrhc = " 右下角點的坐標 "

            };

             

            class Rectangle {

             ...

             

            private:

             std::tr1::shared_ptr<RectData> pData;

                                            // 關于 tr1::shared_ptr 請參見第 13

            };

            因為 Rectangle 的客戶端程序員可能需要了解矩形的區域,所以這個類就應該提供 upperLeft lowerRight 函數。然而, Point 卻是一個用戶自定義的類型,因此你可能會回憶起第 20 條的經驗:通過引用傳遞用戶自定義類型的對象要比直接傳值更高效,這些函數可以返回引用來指向更底層的 Point 對象:

            class Rectangle {

            public:

             ...

             Point& upperLeft() const { return pData->ulhc; }

             Point& lowerRight() const { return pData->lrhc; }

             ...

            };

            這樣的設計可以通過編譯,但是它卻是錯誤的。實際上,它是自我矛盾的。另外,由于 upperLeft lowerRight 的設計初衷僅僅是為客戶端程序員提供一個途徑來了解 Rectangle 的兩個頂點坐標在哪里,而不是讓客戶端程序員去修改它,因此這兩個函數應聲明為 const 成員函數。另外,這兩個函數都返回指向私有內部數據的引用——通過這些引用,調用者可以任意修改內部數據!請看下邊的示例:

            Point coord1(0, 0);

            Point coord2(100, 100);

             

            const Rectangle rec(coord1, coord2);// rec 是一個 Rectangle 常量

                                                // 兩頂點是 (0, 0), (100, 100)

             

            rec.upperLeft().setX(50);            // 但現在 rec 的兩頂點卻變為

                                                // (50, 0), (100, 100)!

            upperLeft 返回了 rec 內部的 Point 數據成員,在這里請注意:雖然 rec 本身應該是 const 的,但是調用者竟可以使用 upperLeft 所返回的引用來修改這個數據成員!

            上面的現象立刻引出了兩個議題:首先,數據成員僅僅與訪問限制最為寬泛的函數擁有同等的封裝性。在這種情況下,即使 ulhc lrhc 聲明為私有的,它們實際上仍然是公共的,這是因為公共函數 upperLeft lowerRight 返回了指向它們的引用。其次,如果一個 const 成員函數返回一個引用,這一引用指向的數據與一個對象相關,但這一數據卻保存在該對象以外,那么函數的調用者就可以修改這一數據。(這樣恰巧超出了按位恒定的范疇——參見第 3 條。)

            我們所做的一切都與返回引用的成員函數有關,但是如果它們返回的是指針或者迭代器,同樣的問題仍然會因為同樣的理由發生。引用、指針、迭代器都可以稱作“句柄”(獲取其它對象的渠道),返回一個指向對象內部部件的句柄,通常都會危及到對象的封裝性。就像我們看到的,即使成員函數是 const 的,返回對象的狀態也是可以任意更改的。

            大體上講,對象的“內部部件”主要是它的數據成員,但是非公用的成員函數同樣也是對象的內部部件。與數據成員相同,返回指向成員函數的句柄也是糟糕的設計。這意味著你不應該讓一個公用成員函數 A 返回一個指向非公用成員函數 B 的指針。如果你這樣做了, B 的訪問權層次就與 A 一樣了,這是因為客戶端程序員將能夠取得 B 的指針,然后通過這一指針來調用它。

            索性的是,返回指向成員函數指針的函數并不常見,所以讓我們還是把精力放在 Rectangle 類和他的 upperLeft lowerRight 成員函數上來吧。我們所發現的關于這些函數所存在的兩個問題都可以簡單的解決,只要將它們的返回值限定為 const 的就可以了:

            class Rectangle {

            public:

             ...

             const Point& upperLeft() const { return pData->ulhc; }

             const Point& lowerRight() const { return pData->lrhc; }

             ...

            };

            使用這一改進的設計方案,客戶端程序員就可以讀取用來定義一個矩形的兩個點,但是他們不可以修改這兩個點。這就意味著將 upperLeft lowerRight 聲明為 const 的并不是一個假象,因為它們將不允許調用者來修改對象的狀態。至于封裝問題,我們一直堅持讓客戶端程序員能能夠看到構造一個 Rectangle 的兩個 Point ,所以說這里我們故意放松了封裝的限制。更重要的是,這一放松是有限的:這些函數僅僅提供了讀的訪問權限。寫權限仍然是禁止的。

            class GUIObject { ... };

             

            const Rectangle boundingBox(const GUIObject& obj); 

            // 以傳值方式返回一個矩形。關于返回值為什么是 const 的,請參見第 3

            現在請考慮一下客戶端程序員可能怎樣來使用這個函數:

            GUIObject *pgo;                 // pgo 指向某個 GUIObject

             

            const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

            // 取得一個指向 boundingBox 左上角點的指針

            調用 boundingBox 將會返回一個新的、臨時的 Retangle 對象。這個對象沒有名字,所以姑且叫它 temp 。隨后 temp 將調用 upperLeft ,然后此次調用將返回一個指向 temp 內部部件的引用,特別地,指向構造 temp 的一個點。 pUpperleft 將會指向這一 Point 對象。到目前為止一切都很完美,但是任務尚未完成,因為在這一語句的最后, boundingBox 的返回值—— temp ——將會被銷毀,這樣間接上會導致 temp Point 被銷毀掉。于是, pUpperLeft 將會指向一個并不存在的對象。這條語句創建了 pUpperLeft ,可也讓它成了孤魂野鬼。

            為什么說:任何返回指向對象內部部件句柄的函數都是危險的,這個問題已經一目了然了。至于句柄是指針還是引用還是迭代器,函數是否是 const 的,成員函數返回的句柄本身是不是 const 的,這一切都無關緊要。只有一點,那就是:只要返回了一個句柄,那么就意味著你正在承擔風險:它可能會比它指向的對象存活更長的時間。

            這并不意味著你永遠也不能讓一個成員函數返回一個句柄。有些時候你不得不這樣做。比如說, operator[] 允許你獲取 string vector 中的任一元素,這些 operator[] 的工作就是通過返回容器內部的數據來完成的(參見第 3 條)——當容器本身被銷毀時,這些數據同時也會被銷毀。然而,這僅僅是一個例外,不是慣例。

            銘記在心

            避免返回指向對象內部部件的句柄(引用、指針或迭代器)。這樣做可以增強封裝性,幫助 const 成員函數擁有更加“ const ”的行為,并且使“野句柄”出現的幾率降至最低。

            青青青青久久精品国产h久久精品五福影院1421 | 婷婷久久综合九色综合绿巨人 | 亚洲乱码精品久久久久..| 国内高清久久久久久| 久久精品视频网| 伊人久久五月天| 2021精品国产综合久久| 久久国产视屏| 一本色道久久99一综合| 国产精品久久影院| 美女久久久久久| 美女写真久久影院| 亚洲精品国精品久久99热一| www亚洲欲色成人久久精品| 中文字幕无码久久精品青草 | 污污内射久久一区二区欧美日韩 | 精品永久久福利一区二区| 久久国产视屏| 色综合久久中文色婷婷| 老色鬼久久亚洲AV综合| 思思久久好好热精品国产| 国产三级观看久久| 国产精品久久网| 天天爽天天狠久久久综合麻豆| 久久综合色之久久综合| 国产亚州精品女人久久久久久 | 久久精品国产一区二区| 久久电影网一区| 久久国产亚洲高清观看| 亚洲av日韩精品久久久久久a| 亚洲一级Av无码毛片久久精品| 国产免费久久精品99久久| 久久青草国产精品一区| 国产精品毛片久久久久久久| 久久99精品国产麻豆宅宅| 国产精品99久久久久久宅男小说| 欧美精品福利视频一区二区三区久久久精品| 国产精品久久久久久久久免费| 九九精品99久久久香蕉| 99久久99久久久精品齐齐| 久久狠狠高潮亚洲精品|