夥伴(friend)是什麽?
让别的类别或函数能存取到你的类别内部的东西。
夥伴可以是函数或其他类别。类别会对它的夥伴开放存取权限。正常情况下,程式员
会下意识、技术性地控制该类别的夥伴与运作行为(否则当你想更动类别时,还得先
有其他部份的拥有者之同意才行)。
「夥伴」违反了封装性吗?
若善用之,反而会「强化」封装性。
我们经常得将一个类别切成两半,当这两半各有不同的案例个数及生命期时。在此情
形之下,它们通常需要直接存取对方的内部(这两半“本来”是在同一个类别里面,
所以你并未“增加”存取资料结构的运作行为个数;你只是在“搬动”这些运作行为
所在之处而已)。最安全的实作方式,就是让这两半互为彼此的「夥伴」。
若你如上述般的使用夥伴,你依然是将私有的东西保持在私有的状态。遇到上述的情
况,如果还呆呆的想避免使用夥伴关系,许多人不是采用公共资料(糟透了!),就
是弄个公共的 get/set 存取运作行为来存取彼此的资料,事实上这些都破坏了封装
性。只有在类别的外面该私有资料「仍有其意义」(以使用者的角度来看)时,开放
出私有资料的存取运作行为才称得上是恰当的做法。多数情况下,「存取运作行为」
就和「公共资料」一样糟糕:它们对私有资料成员只隐其“名”而已,却未隐藏其“
存在”。
同样的,如果将「夥伴函数」做为另一种类别公共存取函数的语法,那就和违反封装
性的成员函数一样破坏了封装。换句话说,物件类别的夥伴及成员都是「封装的界线
」,如同「类别定义」本身一样。
夥伴函数的优缺点?
它提供了某种介面设计上的自由。
成员函数和夥伴函数都有同等的存取特权(100% 的权利),主要的差别在於:夥伴
函数用起来像是 "f(x)",而成员函数则是 "x.f()"。因此,夥伴函数可让物件类别
设计者挑选他看得最顺眼的语法,以降低维护成本。
夥伴函数主要的缺点在於:当你想做动态系结(dynamic binding)时,它需要额外
的程式码。想做出「虚拟夥伴」的效果,该夥伴函数应该呼叫个隐藏的(通常是放在
"protected:" 里)虚拟成员函数;像这个样子:"void f(Base& b) { b.do_f(); }"
。衍生类别会覆盖(override)掉那个隐藏的成员函数("void Derived::do_f()")
,而不是该夥伴函数。
「夥伴关系无继承及递移性」是什麽意思?
夥伴关系的特权性无法被继承下来:夥伴的衍生类别不必然还是夥伴(我把你当朋友,但这不代表我也一定会信任你的孩子)。如果 "Base" 类别宣告了 "f()" 为它的
夥伴,"f()" 并不会自动对由 "Base" 衍生出来的 "Derived" 类别所多出来的部份
拥有特殊的存取权力。
夥伴关系的特权无递移性:夥伴类别的夥伴不必然还是原类别的夥伴(朋友的朋友不
一定也是朋友)。譬如,如果 "Fred" 类别宣告了 "Wilma" 类别为它的夥伴,而且
"Wilma" 类别宣告了 "f()" 为它的夥伴,则 "f()" 不见得对 "Fred" 有特殊的存取
权力。
应该替类别宣告个成员函数,还是夥伴函数?
可能的话,用成员函数;必要时,就用夥伴。
有时在语法上来看,夥伴比较好(譬如:在 "Fred" 类别中,夥伴函数可把 "Fred"
弄成是第二个参数,但在成员函数中则一定得放在第一个)。另一个好例子是:二元
中序式算数运算子(譬如:"aComplex + aComplex" 可能应该定义成夥伴而非成员函
数,因为你想让 "aFloat + aComplex" 这种写法也能成立;回想一下:成员函数无
法提升它左侧的参数,因为那样会把引发该成员函数的物件所属之类别给改变掉)。
在其他情况下,请选成员函数而不要用夥伴函数。