[原创文章欢迎转载,但请保留作者信息]
Justin 于 2009-12-23
先从23条规说起,大师在一开始先给出了为什么推崇非成员函数的理由:
-
从面向对象的角度来看,非成员函数更有利于数据的封装。
数据的封装程度可以这样理解:一个数据成员被越少的代码访问到,该成员的封装程度就越高。我们可以进一步这样理解:越少函数可以直接访问一个数据成员,该成员的封装程度就越高。
如果还记得22课的内容,就知道类的数据成员应该定义为私有。如果这个前提成立,那么能够访问一个数据成员的函数便只能是该类的成员函数,或是友元函数。
于是很容易得到上面的结论:为了更好的封装数据,在可以完成相同功能的前提下,应该优先考虑使用非成员并且非友元函数。
这里的“非成员并且非友元函数”是针对数据成员所在的类而言的,也就是说这个函数完全可以是其他类的成员,只要是不能直接访问那个数据成员就可以。
-
从灵活性上来说,非成员函数更少编译依赖(compilation dependency),也就更利于类的扩展。
先援引大师的例子来说明一下怎样是编译依赖:一个类可能有多个成员函数,可能有一个函数需要A.h,另外一个函数要包含B.h,那么在编译这个类时就需要同时包含A.h和B.h,也就是说该类同时依赖两个头文件。
如果我们使用的是非成员函数咧,这个时候就可以把这些依赖关系不同的函数分别写在不同的头文件中,有可能这个类在编译时就不需要再依赖A.h或是B.h了。
另外一点要注意的是在把这些非成员函数分散定义在不同头文件中的同时,需要用namespace关键字把它们和需要访问的类放在一起。(好歹有点关系,别不住在一起就翻脸不认人了嘛……)
// code in class_a.h
namespace AllAboutClassA {
class ClassA { // ..};
// ..
}
// code in utility_1.h
// ..
namespace AllAboutClassA {
void WrapperFunction_1() { // ..};
// ..
}
// ..
// code in utility_2.h
// ..
namespace AllAboutClassA {
void WrapperFunction_2() { // ..};
// ..
}
// ..
这样一来,虽然这些非成员和类不“住在”一个头文件里,它们的“心”还是在一起的(在同一个名字空间, namespace, 中)。
如果有需要添加新的非成员函数,我们要做的只是在相同的名字空间中定义这些函数就可以,那个类丝毫不会被影响,也即所谓的易扩展性吧。
对于类的用户来说,这样的实现方式(指用非成员函数)就更加合理:
因为作为类的用户,需要扩展类的时候又不能去修改别人的类(版权?安全性?或者根本就没有源码?),就算是通过继承该类的方式也不能访问父类的私有数据。
接下来看看第24条,说的也是非成员非友元函数。Item24的标题比较不直白:”当类型转换需要应用在所有参数的时候,函数应该是非成员函数“,读下来觉得还是无法理解。【以下是自己对此条军规的解读,有待再次拜读时完善】代码在书上,很好很明了。不抄代码了,尝试总结一下:
如果运算符函数的参数有可能发生类型转换,该函数就应该定义为非成员非友元函数。原因是:
此类函数几乎总是隐性调用的:
result = oneHalf * 2; // * is invoked implicitly
而鲜少有下面的显性调用:
result = oneHalf.operator*(2); // * is invoked explicitly
当操作数对象的类型没有定义这一运算符函数时(或是没有定义隐性构造函数, implicit constructor时),就会出错。
如果不明白,就去看例程吧……