尽量以non-member non-friend函数替换成员函数

简介: 尽量以non-member non-friend函数替换成员函数

1.成员函数 VS non-member函数


在类中可能有很多个成员函数,考虑下面的网页浏览器WebBrowser类,该类中有多个成员函数。如:清除下载元素高速缓存区成员函数、清除访问过的URL历史记录的成员函数、移除系统中所有的cookies成员函数。


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


许多用户会想将上面的一系列操作都封装到一个函数中,因此WebBrowser类中也提供了这样的成员函数clearEverything(),如下所示:


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


当然,也可以将上面的一系列操作都封装到一个non-member函数中,在该函数中调用成员函数也可以。如下所示:


void clearBrowser(WebBrowser& wb){  // 非成员函数
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}


上面两种方法,究竟哪一个比较好呢?是成员函数clearEverything()还是non-member函数clearBrowser()?根据面向对象原则要求,数据以及操作数据的那些函数应该被绑定在一块,这意味着建议使用成员函数更好。不幸的是,前面的观点是错误的。它是对面向对象规则的错误理解。面向对象原则的正确理解是:数据应该尽可能被封装成员函数clearEverything()带来的封装性比non-member函数clearBrowser()低。此外,non-member函数可允许对WebBrowser相关机能有较大的包裹弹性,而那最终导致较低的编译依赖度,增加了WebBrowser的可延伸性。


2.为啥non-member函数会胜出?



从面向对象语言的封装性角度来说,如果某些东西被封装,它就不再可见。越多东西被封装,越少的人可以看到它。而越少的人看到它,我们就有越大的弹性可以去改变它,这是因为我们的改变仅仅直接影响能看到改变的那些人和事物。因此,它能使我们能够改变事物而只影响有限客户。
现在考虑对象内部的数据。越少的代码可以“看到”数据,越多的数据就可被封装,而我们也就越能自由地改变对象的内部数据。如何测量“有多少代码可以看到某一块数据”呢?我们计算能够访问该数据的函数数量,以此作为一种粗略的测量。越多的函数可访问它,数据的封装性就越低。
在前面的文章C++类中数据成员要私有说明了成员变量应该是private的必要性,如果它们不是的话,就会有无限量的函数可以访问它们,它们也就毫无封装性可言。能够访问私有成员变量的函数只有类的成员函数和友元函数而已。如果要你在一个成员函数和一个non-member non-friend函数之间抉择,而且两者提供相同的功能,那么导致较大封装性的是non-member non-friend函数,因为它并不增加“能访问类的内部的private成分”的函数数量。因此,这也说明了为什么non-member函数clearBrowser()能胜出的原因:它导致了WebBrowser类有较大的封装性。


3.值得注意的两点



第一,上面的论述只适用于non-member non-friend函数。友元函数对类中的私有成员的访问权限和成员函数相同,因此两者对封装的冲击力相同。从封装的角度来看,这里的选择关键不在于成员函数与non-member函数之间,而在于成员函数与non-member non-friend函数之间。


第二,只考虑封装性而让函数成为类的non-member函数,并不意味着此函数不可以是另一个类的成员函数。例如,我们可以令clearBrowser()函数成为某个工具类的一个静态成员函数。只要它不是WebBrowser类的一部分,就不会影响WebBrowser私有成员的封装性。


4.C++中的做法



在C++中,比较自然的做法是让clearBrowser()函数成为一个non-member函数并且位于WebBrowser类所在的同一个命名空间(namespace)中。如下所示:


namespace WebBrowserStuff{
    class WebBrowser{
        // ...
    };
    void clearBrowser(WebBrowser& wb);  // non-member函数
}

面的做法并不是为了看起来自然而已,本质是命名空间与类不同,命名空间可以跨越多个源码文件而类则不可以。因为像clearBrowser()这样的函数是一个“提供便利的函数”,如果它既不是成员函数也不是友元函数,就没有对WebBrowser的特殊访问权限,也不能提供“WebBrowser客户无法以其他方式取得”的机能。例如,如果clearBrowser()函数不存在,客户端就只好自行调用clearCache()、clearHistory()、removeCookies()。


一个像WebBrowser这样的类中可能有大量的便利函数,如书签便利函数、打印便利函数、cookies管理有关的便利函数。通常,大多数客户只对其中某些感兴趣。为了防止多个便利函数之间发生编译相互依赖性,分离它们的最直接方法是将书签便利函数声明在一个头文件中,将cookies管理有关的便利函数声明在另一个头文件中,再将打印便利函数声明于第三个头文件中。如下所示:


// 头文件webbrowser.h,这个头文件针对WebBrowser类
namespace WebBrowserStuff{
    class WebBrowser{
        // ...
    };
    // ...   non-member函数
}
// 头文件webbrowserbookmarks.h
namespace WebBrowserStuff{
    // ...   与书签相关的便利函数
}
// 头文件webbrowsercookies.h
namespace WebBrowserStuff{
    // ...   与cookies管理相关的便利函数
}

注意:上面的形式正式C++中STL的组织方式。STL并不是拥有单一、整体、庞大的<C++ StandardLibrary>头文件并在其中内含std命名空间内的每一样东西,而是有数十个头文件(<vector>、<list>、<algorithm>、<memory>等),每个头文件声明std的某些机能。如果客户只想使用vector相关机能,他不需要#include <memory>。这允许客户只对他们所用的那一小部分系统形成编译依赖。以此种方式切割机能并不适用于类的成员函数,因为一个类必须整体定义,不能被分割成很多片段。


将所有便利函数放在多个头文件内,但隶属于同一个命名空间,意味着客户可以轻松扩展这一组便利函数。他们需要做的是添加更多的non-member non-friend函数到此命名空间中。这样新的函数就像其他旧的便利函数那样可用且整合为一体,这是类所无法提供的另一个性质,因为类定义式对客户而言是不可扩展的。当然,你可能会说客户使用继承就可以派生出新的类啦,但请记住派生类是无法访问基类中被封装的私有成员。于是,如此的扩展机能拥有的只是次级身份。


5.总结


(1) 宁可拿non-member non-friend函数替换成员函数。这样做可以增加封装性、包裹弹性和机能扩展性。

相关文章
|
12月前
|
安全 C++
【C++11保姆级教程】移动构造函数(move constructor)和移动赋值操作符(move assignment operator)
【C++11保姆级教程】移动构造函数(move constructor)和移动赋值操作符(move assignment operator)
610 0
|
3月前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
4月前
|
编译器 C++
类和对象(6):const成员,&/const &重载
类和对象(6):const成员,&/const &重载
|
5月前
|
C++
C++在构造函数中如何给const成员赋值
C++在构造函数中如何给const成员赋值
|
5月前
|
安全 编译器 C语言
深入了解C++:形参、内联、重载、引用、const和指针、new和delete
深入了解C++:形参、内联、重载、引用、const和指针、new和delete
34 1
|
5月前
|
编译器 C++
在 C++ 中const 成员函数的运用
在 C++ 中const 成员函数的运用
|
5月前
|
C++
c++中const修饰成员函数的问题
问题引入: 看下面这一段代码:
45 0
|
12月前
|
存储 编译器 C语言
C++:类和对象(中)---默认成员函数---运算符重载---const的含义
C++:类和对象(中)---默认成员函数---运算符重载---const的含义
|
C++
C++中的const成员变量和成员函数
在类中,如果你不希望某些数据被修改,可以使用const关键字加以限定。const 可以用来修饰成员变量和成员函数。 const成员变量 const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表,这点在前面已经讲到了,请猛击《C++初始化列表》回顾。 const成员函数(常成员函数) const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。const 成员函数也称为常成员函数。 我们通常将 get 函数设置为常成员函数。
61 1