array(2) { ["docs"]=> array(10) { [0]=> array(10) { ["id"]=> string(3) "428" ["text"]=> string(77) "Visual Studio 2017 单独启动MSDN帮助(Microsoft Help Viewer)的方法" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(8) "DonetRen" ["tagsname"]=> string(55) "Visual Studio 2017|MSDN帮助|C#程序|.NET|Help Viewer" ["tagsid"]=> string(23) "[401,402,403,"300",404]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400964" ["_id"]=> string(3) "428" } [1]=> array(10) { ["id"]=> string(3) "427" ["text"]=> string(42) "npm -v;报错 cannot find module "wrapp"" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "zzty" ["tagsname"]=> string(50) "node.js|npm|cannot find module "wrapp“|node" ["tagsid"]=> string(19) "[398,"239",399,400]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400760" ["_id"]=> string(3) "427" } [2]=> array(10) { ["id"]=> string(3) "426" ["text"]=> string(54) "说说css中pt、px、em、rem都扮演了什么角色" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(12) "zhengqiaoyin" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400640" ["_id"]=> string(3) "426" } [3]=> array(10) { ["id"]=> string(3) "425" ["text"]=> string(83) "深入学习JS执行--创建执行上下文(变量对象,作用域链,this)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "Ry-yuan" ["tagsname"]=> string(33) "Javascript|Javascript执行过程" ["tagsid"]=> string(13) "["169","191"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511399901" ["_id"]=> string(3) "425" } [4]=> array(10) { ["id"]=> string(3) "424" ["text"]=> string(30) "C# 排序技术研究与对比" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "vveiliang" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(8) ".Net Dev" ["catesid"]=> string(5) "[199]" ["createtime"]=> string(10) "1511399150" ["_id"]=> string(3) "424" } [5]=> array(10) { ["id"]=> string(3) "423" ["text"]=> string(72) "【算法】小白的算法笔记:快速排序算法的编码和优化" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "penghuwan" ["tagsname"]=> string(6) "算法" ["tagsid"]=> string(7) "["344"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511398109" ["_id"]=> string(3) "423" } [6]=> array(10) { ["id"]=> string(3) "422" ["text"]=> string(64) "JavaScript数据可视化编程学习(二)Flotr2,雷达图" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "chengxs" ["tagsname"]=> string(28) "数据可视化|前端学习" ["tagsid"]=> string(9) "[396,397]" ["catesname"]=> string(18) "前端基本知识" ["catesid"]=> string(5) "[198]" ["createtime"]=> string(10) "1511397800" ["_id"]=> string(3) "422" } [7]=> array(10) { ["id"]=> string(3) "421" ["text"]=> string(36) "C#表达式目录树(Expression)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "wwym" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(4) ".NET" ["catesid"]=> string(7) "["119"]" ["createtime"]=> string(10) "1511397474" ["_id"]=> string(3) "421" } [8]=> array(10) { ["id"]=> string(3) "420" ["text"]=> string(47) "数据结构 队列_队列实例:事件处理" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "idreamo" ["tagsname"]=> string(40) "C语言|数据结构|队列|事件处理" ["tagsid"]=> string(23) "["246","247","248",395]" ["catesname"]=> string(12) "数据结构" ["catesid"]=> string(7) "["133"]" ["createtime"]=> string(10) "1511397279" ["_id"]=> string(3) "420" } [9]=> array(10) { ["id"]=> string(3) "419" ["text"]=> string(47) "久等了,博客园官方Android客户端发布" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(3) "cmt" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511396549" ["_id"]=> string(3) "419" } } ["count"]=> int(200) } 222 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对封装性的破坏几乎是毁灭性的。

相关文章: