1. 程式人生 > >C++中friend對類封裝性的強大破壞性

C++中friend對類封裝性的強大破壞性

class ClxSingletonMEC
{
public:
    friend ClxSingletonMEC
& InstanceMEC();

private:
    ClxSingletonMEC() {};
    ClxSingletonMEC(
const ClxSingletonMEC &lxSington) {};
};

ClxSingletonMEC
& InstanceMEC()
{
    
static ClxSingletonMEC Instance;
    
return Instance;

    並指出,為了防止這個類的使用者寫出下面的程式碼:

ClxSingletonMEC lxMEC 
= InstanceMEC(); // 或者ClxSingletonMEC lxMEC(InstanceMEC());

    類ClxSingletonMEC的拷貝建構函式必須是private的。
    而purewinter在評論中說道:“還需要把解構函式設為private,否則使用者可以錯誤的delete掉它。”我本來想回復他說,如果解構函式也為private的,那麼在程式退出時候呼叫類的解構函式時就會出現私有成員函式不能訪問的錯誤。
    不過我在回覆之前做了個試驗。沒想到的是,把類ClxSingletonMEC的解構函式設定為private後,程式能編譯通過,並且能正常執行!通過在這個private的解構函式中設定斷點我發現,這個private的解構函式確實被正常的呼叫了。
    上面的結果令我非常吃驚!同時也非常迷惑!為什麼一個private的解構函式能被正常的呼叫呢?!這完全推翻了我以前對C++中訪問許可權(pbulic,protected,private)的理解。
    如果說物件Instance不是靜態的,比如函式InstanceMEC()是這樣的:

void InstanceMEC()
{
    ClxSingletonMEC Instance;

    
// 下面省略……}

    那麼,物件Instance在函式InstanceMEC()作用域結束的時候會被析構。這個時候,系統呼叫物件Instance的解構函式,因為函式InstanceMEC()是類ClxSingletonMEC的友元函式,所以可以訪問類所有的成員函式,因此可以呼叫私有的解構函式將物件Instance析構。這個非常好理解。
    可是,在Singletion模式的例子程式碼中,類ClxSingletonMEC的友元函式InstanceMEC()中的物件Instance是一個靜態物件。而靜態物件(不管是全域性的還是區域性的)是一經構造,就存放在程序中的一個固定記憶體中,直到程序結束的時候才會由系統呼叫物件的解構函式而被析構掉。這也是上面的例子程式碼能保證Instance在程序中唯一的原因(不管使用者呼叫多少次函式InstanceMEC(),只有第一次呼叫是真正的構造物件Instance,其他的是直接返回物件Instance,這也是static的特性)。也就是說,物件Instance被不是在函式InstanceMEC()中被析構的。那為什麼物件Instance的私有解構函式還能被呼叫呢?
    經過一段時間的思索和程式碼測試,我發現了“罪魁禍首”--friend。在C++中,friend是破壞封裝性的,友元函式可以不受訪問許可權的限制而訪問類的任何成員。在Singletion模式的例子程式碼中,這正是利用了友元函式的這個特性來訪問類的私有建構函式來建立類在程序中的唯一物件的。而C++的訪問許可權僅僅在原始檔中有效,編譯時C++編譯器確保訪問規則,但編譯後的目標檔案和庫檔案裡是沒有任何訪問許可權資訊的。也就是說,由於物件Instance宣告在類ClxSingletonMEC的友元函式InstanceMEC()內,在編譯階段編譯器就生成了系統可以訪問物件Instance的私有建構函式和私有解構函式的目標檔案(至於是什麼時候呼叫則是執行期確定的)。而物件Instance是靜態的,會存放在程序中的一個固定記憶體中,是執行時期來決定的。在程序結束時,系統要清空程序堆空間,在呼叫物件Instance的解構函式時是不會判斷該解構函式是否為私有(檔案中根本沒有任何訪問許可權資訊),因為在編譯時期就已經設定物件Instance的私有解構函式是可以被呼叫的。
    由此可見,C++中friend對封裝性的破壞幾乎是毀滅性的。