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

2024-08-28 04:18

本文主要是介绍条款23 宁以non-member、non-friend替换member函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

总结:

    用非成员非友元函数取代成员函数。这样做可以提高封装性,包装弹性,和机能扩充性


      想象一个用来表示网页浏览器浏览器的类。这样一个类可能提供的大量函数中,有一些用来清空下载元素高速缓存区、清空访问过的URLs历史,以及从系统移除所有cookies的功能:

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

很多用户希望能一起执行全部这些动作,所以WebBrowser可能也会提供一个 成员函数去这样做:

class WebBrowser {
public:...void clearEverything();// calls clearCache, clearHistory, and removeCookies...
};

当然,这个功能也能通过 非成员函数调用适当的成员函数来提供:

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

那么哪个更好呢,成员函数clearEverything还是非成员函数clearBrowser?

        面向对象原则指出:数据和对它们进行操作的函数应该被绑定到一起,而且建议成员函数是更好的选择。不幸的是,这个建议是不正确的。面向对象原则指出数据应该尽可能被封装,与直觉不符的是,成员函数clearEverything居然会造成比非成员函数clearBrowser更差的封装性。此外,提供非成员函数允许WebBrowser相关功能的更大的包装弹性,可以获得更少的编译依赖和增加WebBrowser的扩展性。因而,在很多方面非成员函数比成员函数更好。

        我们将从封装开始。封装为我们提供一种改变事情的弹性,而仅仅影响有限的客户。结合对象内的数据考虑,越少有代码可以看到数据(访问它),数据的封装性就越强,我们改变对象数据的的自由也就越大,比如,数据成员的数量、类型,等等。如何度量有多少代码能看到数据呢?我们可以计算能访问数据的函数数量:越多函数能访问它,数据的封装性就越弱

       我们说过数据成员应该是private,否则它们根本就没有封装。对于private数据成员,能访问他们的函数数量就是类的成员函数加上友元函数,因为只有成员和友元函数能访问 private成员。假设在一个成员函数(能访问的不只是一个类的private数据,还有 private 函数,枚举,typedefs等等)和一个提供同样功能的非成员非友元函数(不能访问上述那些东西)之间选择,能获得更强封装性是非成员非友元函数。这就解释了为什么clearBrowser(非成员非友元函数)比clearEverything(成员函数)更可取:它能为WebBrowser获得更强的封装性。

         在这一点,有两件事值得注意。首先,这个论证只适用于非成员非友元函数。友元能像成员函数一样访问一个类的private成员,因此同样影响封装。从封装的观点看,选择不是在成员和非成员函数之间,而是在成员函数和非成员非友元函数之间。第二,只因关注封装而让函数成为类的非成员并不意味着它不可以是另一个类的成员。这对于习惯了所有函数必须属于类的语言(例如,Eiffel,Java,C#等等)的程序员是一个适度的安慰。例如,我们可以使clearBrowser成为某工具类的static成员函数,只要它不是WebBrowser的一部分(或友元),它就不会影响WebBrowser的private成员的封装。

         在C++中,比较自然的做法是使clearBrowser成为与 WebBrowser在同一个namespace中的非成员函数:

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

         namespace能跨越多个源文件而类不能。这是很重要的,因为类似clearBrowser的函数是提供便利的函数。如果既不是成员也不是友元,他们就没有对WebBrowser的特殊访问权力,所以不能提供任何一种WebBrowser客户无法以其它方法得到的机能。例如,如果clearBrowser不存在,客户可以直接调用clearCache,clearHistory和 removeCookies本身。

        一个类似WebBrowser的类可以有大量的方便性函数,一些是书签相关的,另一些打印相关的,还有一些是cookie管理相关的,等等。通常多数客户仅对其中一些感兴趣。没有理由让一个只对书签相关便利函数感兴趣的客户在编译时依赖其它函数。分隔它们直截了当的方法就是将头文件分开声明:

// header "webbrowser.h" – 针对WebBrowser自身及其核心机能
namespace WebBrowserStuff {class WebBrowser { ... };... // 核心机能,如人人都会用到的non-member函数
}// header "webbrowserbookmarks.h"
namespace WebBrowserStuff {... // 书签相关的便利函数
}// header "webbrowsercookies.h"
namespace WebBrowserStuff {... // cookie相关的便利函数
}...

        这正是C++标准程序库的组织方式。标准程序库并不是拥有单一整体而庞大的<C++StandardLibrary>头文件并内含std namespace中的所有东西,它们在许多头文件中(例如,<vector>,<algorithm>,<memory>等等),每一个都声明了std中的一些机能。这就允许客户在编译时仅仅依赖他们实际使用的那部分系统。当机能来自一个类的成员函数时,用这种方法分割它是不可能的,因为一个类必须作为一个整体来定义,它不能四分五裂。

        将所有方便性函数放入多个头文件中,但隶属于一个namespace中,意味着客户能容易地扩充便利函数的集合,要做的只是在namespace中加入更多的非成员非友元函数。例如,如果一个 WebBrowser的客户决定写一个关于下载图像的便利函数,仅仅需要新建一个头文件,包含那些函数在WebBrowserStuff namespace中的声明,这个新函数现在就像其它便利函数一样可用并被集成。这是类不能提供的另一个特性,因为类定义对于客户是不能扩展。当然,客户可以派生新类,但是派生类不能访问基类中被封装的(private)成员,所以这样的“扩充机能”只是次等身份。






这篇关于条款23 宁以non-member、non-friend替换member函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1113707

相关文章

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C++11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆,该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使用了由[90]描述的第一个算法。开发者应该注意,由于数据点靠近包含的 Mat 元素的边界,返回的椭圆/旋转矩形数据

Unity3D 运动之Move函数和translate

CharacterController.Move 移动 function Move (motion : Vector3) : CollisionFlags Description描述 A more complex move function taking absolute movement deltas. 一个更加复杂的运动函数,每次都绝对运动。 Attempts to

✨机器学习笔记(二)—— 线性回归、代价函数、梯度下降

1️⃣线性回归(linear regression) f w , b ( x ) = w x + b f_{w,b}(x) = wx + b fw,b​(x)=wx+b 🎈A linear regression model predicting house prices: 如图是机器学习通过监督学习运用线性回归模型来预测房价的例子,当房屋大小为1250 f e e t 2 feet^

JavaSE(十三)——函数式编程(Lambda表达式、方法引用、Stream流)

函数式编程 函数式编程 是 Java 8 引入的一个重要特性,它允许开发者以函数作为一等公民(first-class citizens)的方式编程,即函数可以作为参数传递给其他函数,也可以作为返回值。 这极大地提高了代码的可读性、可维护性和复用性。函数式编程的核心概念包括高阶函数、Lambda 表达式、函数式接口、流(Streams)和 Optional 类等。 函数式编程的核心是Lambda

PHP APC缓存函数使用教程

APC,全称是Alternative PHP Cache,官方翻译叫”可选PHP缓存”。它为我们提供了缓存和优化PHP的中间代码的框架。 APC的缓存分两部分:系统缓存和用户数据缓存。(Linux APC扩展安装) 系统缓存 它是指APC把PHP文件源码的编译结果缓存起来,然后在每次调用时先对比时间标记。如果未过期,则使用缓存的中间代码运行。默认缓存 3600s(一小时)。但是这样仍会浪费大量C