一. 内容
-
举个例子,当你有个 class 表示浏览器时,有众多功能需要提供,其中可能有清除下载元素的高速缓存区,清除访问过的 URLs,清除系统中所有的 cookies。
示例代码class WebBrowser { public: void ClearCache(){std::cout<<"clear cache"<<"\n";} void ClearHistory(){std::cout<<"clear history"<<"\n";} void ClearCookies(){std::cout<<"clear cookies"<<"\n";} void ClearEverything() { ClearCache(); ClearHistory(); ClearCookies(); } }; inline void ClearWebBrowserEverything(WebBrowser& WebBrowser) { WebBrowser.ClearCache(); WebBrowser.ClearHistory(); WebBrowser.ClearCookies(); }
问题来了,
如果想要提供给用户清除所有的功能,究竟是使其成为 member 函数,还是 non-member 函数?
两者似乎都可以实现功能。 -
面向对象守则要求:数据应该和操作数据的那些函数捆绑在一块,这意味着它建议 member 函数是较好的选择。不幸的是,这是对基于对象真实含义的一个误解。
面向对象守则要求数据尽可能被封装
,而与直观相反的是,non-member 函数带来的封装性要比 member 函数高
。此外,提供 non-member 函数可以提供更多的机能上的包裹弹性,而且可以降低编译相依度,增加对象的可延伸性。 -
让我们从封装开始讨论。如果某些东西被封装,它就不再可见。愈多东西被封装,愈少人可以看见它们。而愈少人看见,我们就有愈大的弹性去改变它们,因为我们改变仅仅影响到看到的那些人的事物。因此
愈多东西被封装,我们改变事物的能力就愈大
。这也是我们推崇封装的原因。 -
现在考虑对象内的数据,愈多函数可以访问它们,数据的封装性就越低。条款22曾说,成员变量应该是 private,能访问 private 的成员函数就只有 class 的 member 函数和 friend 函数。所以如果 member 函数和 non-member,non-friend 函数可以提供相同的机能,那么能提供较大封装性的应该是后者。
-
注意
non-member 函数不意味着它不可以是另一个 class 的 member 函数
, -
non-member 函数在 C++ 中比较自然的做法是
让 class 和 non-member 函数在一个 namespace(命名空间)
:namespace WebBrowserStuff { class WebBrowser { public: void ClearCache(){std::cout<<"clear cache"<<"\n";} void ClearHistory(){std::cout<<"clear history"<<"\n";} void ClearCookies(){std::cout<<"clear cookies"<<"\n";} }; inline void ClearWebBrowserEverything(WebBrowser& WebBrowser) { WebBrowser.ClearCache(); WebBrowser.ClearHistory(); WebBrowser.ClearCookies(); } }
当然不只是为了自然一些,要知道 namespaces 和 classes 不同,
前者可以跨越多个源文件
,而后者不能。这很重要,意味着可以在不同的源文件提供这些 non-member 便利函数。注意这正是 C++ 标准库的组织方式。标准程序库并不是拥有单一、整体、庞大的 <C++ StandardLibrary> ,内含std命名空间的每一项东西,而是有数十个头文件(<vector>,<algorithm>,<memory>),每一个头文件声明一定的机能,用户只需引用所必要的头文件便可使用对应的功能。
二. 总结
- 宁可拿 non-member non-friend函数替换 member 函数。这样做可以增加封装性、包裹弹性(package flexibility)和机能扩展性。