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

(一)

有个class来表示网页浏览器:

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

许多用户会想一整个执行所有这些动作,因此WebBrowser也提供这样一个函数:clearEverything

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

当然也可以由一个non-member函数调用适当的member函数而提供出来:

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

那么哪一个比较好呢?是member函数clearEverything()还是non-member函数claseBrower呢。?

越多东西被封装,我们改变那些东西的能力也就越大。对象内的数据。越少代码看到它(访问它),越多的数据可被封装,也就越能自由地改变对象数据,例如改变成员变量的数量,类型等等。

所以答案当然是non-member函数claseBrower!因为它导致了较大的封装性,因为它并不增加“能过访问class内之private成分”的函数数量。这就解释了为什么clearBrowser(一个non_member non-friend函数)比clearEverything(一个member函数)更受欢迎的原因:它导致WebBrowser class有较大的封装性。

但是这里有两件事情要注意下:

第一:这里只适用于non-member non-friend函数。friend函数对class private成员的访问权力和member函数相同,因此两者对封装的冲击也相同。

第二:只因在意封装而让函数“成为class 的non-member”,并不意味着它“不可以是另一个class的member”。假如我们可以另clearBrowser成为某工具类(utility class)的一个static member函数。

(二)

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

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

像clearBrowser这样的函数是个“提供便利的函数”。一个像WebBrowser这样的class可能拥有大量的便利函数,某些与书签有关,某些与打印有关,某些与cookie的管理有关…,通常,大多数客户只对其中某些感兴趣。没有道理一个只对书签感兴趣的客户却与一个cookie相关便利函数发生编译相依关系。分离他们最直接的方法是将他们放入不同的头文件:

//头文件webbrowser.h这一头文件针对class WebBrowser自身以及WebBrowser核心机能。
namespace WebBrowserStuff{
    void clearBrowser(WebBrowser wb);
    ...//WebBrowser核心机能,几乎所有客户都需要的non-member函数。
}

//头文件“webbrowserbookmarks.h”

namespace WebBrowserStuff{
    class WebBrowser{...};
    ...//与书签相关的便利函数
}

//头文件“webbrowsercookies.h”

namespace WebBrowserStuff{
    class WebBrowser{...};
    ...//与cookie相关的便利函数
}

C++标准程序库正是这样的组织方式。数十个头文件,每个头文件声明std的某些机能。如果只想使用vector不用#include <memory>;如果不想使用list也不需要#include <list>.这允许客户只对他们所用的那一小部分系统形成编译相依。这种切割机能并不适合class成员函数,因为class 必须整体定义,不能被分割为片段。namespace可以跨越多个源码文件,而class不能。

将所有便利函数放在多个头文件中但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。他们所要做的就是添加更多non-member non-friend函数到此命名空间内。例如:如果客户想写些与影像下载相关的便利函数,只要在WebBrowserStuff命名空间内建立一个头文件,内含那些函数的声明即可。新函数就像其他旧函数一样可用且整合为一体。这是class无法提供的另一个性质,因为class定义对客户是不能扩展的。

请记住:

尽量用non-member non-friend函数替换member函数。可以增加封装性、包裹弹性(packaging flexibility)、和机能扩充性。

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

时间: 2025-01-01 14:56:41

Effective C++:条款23:宁以non-member、non-friend替换member函数的相关文章

effective c++ 条款23 perfer nonmember nonfreind function to member function

主要的理由还是封装.nonmember nonfreind function 不能访问类private 成员变量. 这个场景是有一个类提供了一些基本功能,比如 class WebBrowser { public: void clearCache(); void clearHistory(); void removeCookies(); }; 有时候我们需要执行上述三个函数.我们的做法是 void clearBrowser(WebBrowerser &wb) { wb.clearCache();

Effective C++ 条款六 若不想使用编译器自动生成的函数,就该明确拒绝

class HomeForSale //防止别人拷贝方法一:将相应的成员函数声明为private并且不予实现 { public: private: HomeForSale(const HomeForSale&); HomeForSale& operator = (const HomeForSale&);//只有申明,此函数很少被使用   };   //方法二,设计一个专门用来阻止copying动作的基类,然后让其他类继承这个类即可   class Uncopyable { prot

Effective C++ 条款25 考虑写出一个不抛出异常的swap函数

1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下: namespace std{ template<typename T> swap(T& lhs,T& rhs){ T temp(lhs); lhs=rhs; rhs=temp; } } 只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某

Effective C++ 条款23 宁以non-member,non-friend替换member函数

1. 面向对象的真实意义并非是数据以及操作数据的函数应该被捆绑在一起,而是要求数据应该尽可能地被封装.封装意味着数据的不可见,越多的东西被封装,用户对其直接的接触就越少,用户代码和被封装内容的编译相关度就越低,"包裹弹性"就越高,也就是说,封装性越好,对代码的更改所造成的影响就越低. 2. non-member-non-friend函数实际上比public-member和friend函数要高,因为前者无法访问类的private对象,而后者可以访问类的任何对象,这显然降低了数据的封装性(

Effective C++ 条款23

宁non-member.non-friend顶替member性能 本节介绍笔者为什么时间来实现某些功能.择非成员函数而且是非友元函数.这样做总结一句话,就是最大限度的实现类的封装性. 封装意味着不可见. 愈多东西被封装.欲少人能够看到它,我们就有愈大的弹性去改变它.愈少代码能够看到数据(訪问数据),愈多数据可被封装,我们就更有自由来改变对象数据.愈多函数能够訪问它,数据的封装性就愈低. 我们知道在上一节声明private数据成员也是为了实现类的封装.可见封装对于一个健壮的类来说的重要性. 以上就

More Effective C++ 条款23 考虑使用其他程序库

1. "理想的程序库应该小,快速,威力强大,富有弹性,有扩展性,直观,可广泛运用,有良好支持,使用时没有束缚,而且没有'臭虫'".但实际上这种程序库是不可能实现的:要针对速度和大小做优化,往往要牺牲移植性;要有丰富的机能,结果可能不够直观......一个程序库往往要权衡各方面得失,采取折中的方法来实现. 2. 不同的程序库侧重点可能不一样,即使两个程序库机能类似,也可能有不同的性能表现. 考虑iostream和stdio程序库,stdio提供的I/O操作速度通常比iostream快,可

Effective C++ 条款50 了解new和delete的合理替换时机

1. 替换标准库提供的operator new或operator delete通常基于以下三个理由: 1). 用来检测运行上的错误.将"new 所得内存"delete掉却不幸失败会导致内存泄露,多次对同一块"new所得内存"施行delete会导致未定义行为,如果让operator new持有一串动态分配所得地址,而operator delete将地址从中移走,就可以很容易检测出上述错误;各式各样的变成错误会导致数据"overruns"(写入点在分

Effective C++ 条款46 需要类型转换时请为模板定义非成员函数

1. 条款24举出一个Rational的例子,来说明为什么只有non-member函数才有能力"在所有实参身上实施隐式类型转换".Rational的定义如下: class Rational{ public: Rational(int numerator=0,int denominator=1); int numerator()const; int denominator()const; private: int numerator; int denominator; }; operat

Effective C++ 条款6 若不想使用编译器自动生成的函数,就该明确拒绝

1. 某些类的含义决定了它们不具备某些功能,也就是说某些函数不能被创造出来以防被错误的使用(例如定义一个Book类,它含有一个表示ISBN的变量,这种情况下拷贝构造函数以及赋值操作符显然是没有意义的,因为任何两种书的ISBN都不同),但是编译器在类的创建者没有声明默认构造函数,拷贝构造函数,赋值操作符和析构函数的情况下会产生这些函数,为了避免这种情况,可以将这些函数声明为private并且不提供它们的定义来阻止它们的使用,但是如果这些函数经由其他成员函数或者友元函数调用,错误将会在链接期才能被发