C++中friend对类封装性的强大破坏性

写这篇文章的动机来源于网友purewinter在我的那篇《重读《设计模式》之学习笔记(三)--SINGLETON模式的疑惑》中的评论。
    在那篇文章中,我提供了如下一个用C++实现的Singletion模式的小例子:

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对封装性的破坏几乎是毁灭性的。
posted on 2010-05-31 23:39  carekee  阅读(396)  评论(0编辑  收藏  举报