条款23和条款24

简介: 条款23和条款24

条款23:宁以non-member、non-friend替换member函数

为什么要用非成员函数、非友元函数替换成员函数呢?其实这是为了保证数据的封装性。而数据的封装性强弱是怎么体现的呢?一种粗糙的量测,我们认为越多的函数能访问它,数据的封装性就越低,因为如果数据发生改变,因它的改变牵扯到需要改变的太多,所以它的封装性较差。

而非成员函数、非友元函数较成员函数而言,访问不到private中的数据,数据的封装性自然就更好一些。也就是说,越多的函数可以访问,数据的封装性就越低。

需要注意的是,只因在意封装性而让函数“成为class的non-member”,并不意味它“不可以是另一个class的member”。该函数可以成为另一个类的static member函数,只要不影响原类中private成员的封装性。

看一下书中的例子:

class WebBrowser {
public:
  void clearCache();
  void clearHistory();
  void removeCookies();
}

现在有这样一个类,类中提供三种方法用来清除web浏览器的一些缓存。如果我们想一起清除Cache、History、Cookies,我们可以将这三个函数放在一个成员函数中去做,这样每次只需调用一个函数就行。

class WebBrowser{
public:
  void clearEverything(); //调用clearCache(),clearHistory(),removeCookies()
}

clearEverything()是一个成员函数,条款中不是说宁以non-member、non-friend替换member吗?因此,可以这样做:

void clearBrowser(WebBrowser &wb) {
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

让它成为一个非成员函数。

而在c++中我们通常可以将类和有关该类的非成员函数放在同一命名空间中,就像这样:

namespace WebBrowserStuff {
  class WebBrowser {...};
  void clearBrowser(WebBrowser& wb);
  ...
}

同时,与WebBrowser类有关的便利函数,可能有多个,以及多个种类,比如,与cookie相关的,还有与书签相关的等等。为了减少没必要的依赖(比如,我现在只想用与cookie相关的函数,也就没必要引入与书签相关的函数),我们可以将他们分别声明在不同的头文件中,但都属于WebBrowserStuff这一命名空间,因为,他们都是与WebBrowser有关的。

//头文件“webbrowser.h”这个头文件针对class WebBrowser自身及WebBrowser核心机能。
namespace WebBrowserStuff {
  class WebBrowser {...};
  ...   //核心机能,例如几乎所有客户都需要的non-member函数
}
//头文件“webbrowserbookmarks.h”
namespace WebBrowserStuff {
  ...   //与书签相关的便利函数
}
//头文件“webbrowsercookies.h”
namespace WebBrowserStuff {
  ...   //与cookie相关的便利函数
}

将便利函数放在多个头文件中但都属于同一命名空间,意味着我们可以轻松的在此基础上增加其他的便利函数,这也是命名空间较class好的一点,class是一个整体,对于用户来说不能扩展,而命名空间可以。

条款24:若所有参数皆需类型转化,请为此采用non-member函数

现在我们定义一个有理数的类:

class Rational {
public:
  Rational(int numerator = 0, int denominator = 1);
  int numerator() const;
  int denominator() const;
private:
  ...
}

有理数相乘似乎是很正常的事,为此,在类中重载一下乘法运算:

class Rational {
public:
  ...
  const Rational operator* (const Rational &rhs) const;
}

这样便可支持两个有理数相乘:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth;
result = result * oneEighth;

让有理数和整数相乘似乎也是很正常的事,当我们执行:

result = oneHalf * 2;//正确
result = 2 * oneHalf;//错误

可以发现,其中一个报错,这是因为,oneHalf是一个内含operator*函数的class的对象,所以编译器调用该函数。而整数2没有相应的class,也就没有operator*成员函数。编译器尝试非成员函数operator*(也就是在命名空间内或在global作用域内),然而其他地方也没有相关定义,因此报错。再来看result = oneHalf * 2,这个为什么正确呢?其实这里涉及到一个隐式的类型转换,即2通过构造函数转换为一个Rational类型,两个Rational类型相乘,自然没有报错。

只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者,正如result = oneHalf * 2一样。对于出错的情况,我们可以重载一个非成员函数的乘法运算:

class Rational {
  ...
};
const Rational operator*(const Rational& lhs, const Rational& rhs) {
  return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

其实上述两种重载方式称为类内重载和类外重载,当两种重载方式同时存在时,编译器会优先使用类内重载,因为类内重载对于类的使用者来说是优先可见的,因此它的优先级更高(对于类的使用者而言,优先看到的肯定是意图中想用的)。

相关文章
|
5天前
|
安全
AIGC知识产权赔偿条款
【2月更文挑战第9天】AIGC知识产权赔偿条款
31 3
AIGC知识产权赔偿条款
|
5天前
|
编译器 C++
条款6和条款7
条款6和条款7
|
5天前
|
编译器 C++
条款5和条款16
条款5和条款16
|
5天前
条款9和条款22
条款9和条款22
|
5天前
|
编译器 C语言 C++
条款20和条款21
条款20和条款21
|
5天前
|
C++
条款10和条款13
条款10和条款13
|
7月前
|
供应链 安全 数据安全/隐私保护
什么是合同签订后的情势变更
什么是合同签订后的情势变更
33 0
阿里云软件著作权登记申请流程
阿里云软件著作权登记申请流程,阿里云计算机软件著作权登记20天下证,那么如何申请阿里云软件著作权登记呢?阿里云百科来详细说下注册账号、实名认证、软件著作权选择、信息填写、纸质材料邮寄一直到版权中心审查和下证详细说明:
351 0
阿里云软件著作权登记申请流程
软件著作权申请流程及费用by阿里云
阿里云计算机软件著作权登记20天下证,那么如何申请阿里云软件著作权登记呢?阿里云百科来详细说下注册账号、实名认证、软件著作权选择、信息填写、纸质材料邮寄一直到版权中心审查和下证详细说明:
205 0
软件著作权申请流程及费用by阿里云
阿里云软件著作权申请流程及费用
阿里云软件著作权申请流程及费用,阿里云计算机软件著作权登记20天下证,那么如何申请阿里云软件著作权登记呢?阿里云百科来详细说下注册账号、实名认证、软件著作权选择、信息填写、纸质材料邮寄一直到版权中心审查和下证详细说明:
161 0
阿里云软件著作权申请流程及费用