条款23:宁以non-member、non-friend替换member函数
Prefer non-member non-friend functions to member.
在上一部分我们知道:
- non-member non-friend函数比member函数更具备封装性。
而对于这一点,有两件事情值得注意:
(1)这个论述仅适用于non-member non-friend函数。friends函数对class private成员的访问权利和member函数相同。因此,从封装的角度来看,这里的选择关键并不在于member和non-member函数之间,而在于member和non-member non-friend函数之间。
当然,封装并非我们的唯一考量。当我们考虑隐式类型转换时,就应该在member和non-member函数间进行选择了。
(2)只因为在意封装性而让函数“变成class的non-member”,并不意味着它“不可以是另一个class的member。”例如,我们可以令clearBrowser称为某个工具类(utility class)的一个static member函数,只要它不是WebBrowser的一部分(或者称为其friend),就不会影响WebBrowser的private成员封装性。
在C++中,比较自然地做法是:
- 让clearBrowser成为一个non-member函数,并且位于WebBrowser所在的同一个namespace(命名空间)中。
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);
...
}
然而,这不仅仅是看起来自然而已。
- namespace和class不同,前者可以跨越多个源码文件而后者不可以。
这一点很重要,因为clearBrowser这个函数是个“提供便利的函数”,如果它既不是member或friend。它就没有对WebBrowser的特殊访问权利!
一个像WebBrowser这样的class可能会拥有大量的便利函数,某些与书签有关,某些与打印有关,某些则与cookie管理有关。。。然而,大多数情况下用户只对其中某一个感兴趣。
因此,分离它们的最直接的办法就是讲某一个相关函数声明在一个头文件内,将另一个相关函数声明在另一个头文件中:
//头文件webbrowser.h,这个头文件针对class WebBrowser自身以及WebBrowser核心机能
namespace WebBrowserStuff {
class WebBrowser { ... };
... //核心机能,例如几乎所用用户都会用到的non-member函数
}
//头文件webbrowserbookmarks.h
namespace WebBrowserStuff {
... //与书签相关的便利函数
}
//头文件webbrowsercookies.h
namespace WebBrowserStuff {
... //与cookie相关的便利函数
}
值得注意的是,这正是C++标准程序库的组织方式。
标准程序库并不是拥有单一、整体、庞大的< C++StandardLibrary >头文件并在其中内含std命名空间内的每一样东西,而是有数十个头文件(< vector >, < algorithm >, < memory> 等等),每个头文件声明std的某些机能。
如果一个用户只想使用vector的相关机能,他并不需要去#include <memory>
于是,这就允许用户只对他们所用的那一小部分系统形成编译相依。然而:
- 这种切割并不使用于class成员函数,因为一个class必须整体定义,不能被分割为片段。
简而言之,将所有便利函数放在多个头文件内但是隶属于同一个命名空间,意味用户可以轻松扩展这一组便利函数。他们需要做的仅仅是添加更多non-member non-friend函数到这个命名空间中。
这一点是class无法提供的另一个性质,因为class定义式对于用户而言是不可扩展的。尽管用户可以派生出新的classes,但是derived classes无法访问base class中被封装(即private)成员,于是此时的“扩展机能”拥有的只是次级身份。另外,在条款7中也讲到:
- 并非所有的class都被设计作为base classes。
最后: