Item 23:非成员非友元函数好于成员函数 Effective C++笔记
2015-09-07 14:19
351 查看
Item 23: Prefer non-member non-friend functions to member functions
在类的是实现中,常常会面临成员函数和非成员函数的选择。比如一个浏览器类:
此时你要实现一个
哪种更好呢?面向对象原则指出,数据和数据上的操作应当绑定在一起,那么前者更好。 这是对面向对象的误解,面向对象设计的精髓在于封装,数据应当被尽可能地封装。 相比于成员函数,非成员函数提供了更好的封装,包的灵活性(更少的编译依赖),以及功能扩展性。
封装就是对外界隐藏的意思。如果数据被越好地封装,那么越少的东西可以看到它,我们便有更大的灵活性去改变它。这是封装带来的最大的好处:给我们改变一个东西的灵活性,这样的改变只会影响到有限的客户。
作为粗粒度的估计,数据的封装性反比于可访问该数据的函数数量。这些函数包括成员函数、友元函数和友元类中的函数。 因此非成员非友元函数会比成员函数提供更好的封装, 我们应该选择
Item22提到,如果一个数据成员不是私有的,那么将会有无限数量的函数可访问它。
这里有两点值得注意:
友元函数和成员函数是一样的,因为友元函数也可以访问私有数据成员,它和成员函数对封装具有相同的影响。
非成员函数并不意味着它不可以是其他类的成员函数。尤其是在像Java,C#之类的语言中,函数必须定义在类中。
静态成员函数也是不错的选择。因为静态函数不能访问对象成员,因此不会影响对象的封装。
在C++中,可以把这些非成员函数定义在相同的命名空间下。 但问题又来了:这些在命名空间下的函数并不在类中,它们会被传播到所有的源文件中。 而客户并不希望为了使用几个工具函数,就对这样一个庞大的命名空间产生编译依赖。 因此我们可以将不同类别的工具函数放在不同的头文件中,客户可以选择它想要的那部分功能:
这也是C++标准库的组织方式,
为了做到这一点,这些工具函数必须是非成员函数,因为类作为整体必须在一个文件中进行定义。
同一命名空间不同头文件的组织方式,也为客户扩展工具函数提供了可能。 客户可以在同一命名空间下定义他自己的工具函数, 这些函数便会和既有工具函数天然地集成在一起。 这也是成员函数无法做到的一个特性,因为类的定义对客户扩展是关闭的。 即使是子类也不能访问封装的(私有)成员数据, 况且有些类不是用来做基类的(见Item
7:将多态基类的析构函数声明为虚函数)。
除非注明,本博客文章均为原创,转载请以链接形式标明本文地址: http://harttle.com/2015/08/20/effective-cpp-23.html
在类的是实现中,常常会面临成员函数和非成员函数的选择。比如一个浏览器类:
class WebBrowser{ public: void clearCache(); void clearCookies(); void clearHistory(); };
此时你要实现一个
clearEverything()有两种方式:
class WebBrowser{ public: void clearEverything(){ clearCache(); clearCookies(); clearHistory(); } } // 或者使用非成员函数: void clearEverything(WebBrowser& wb){ wb.clearCache(); wb.clearCookies(); wb.clearHistory(); }
哪种更好呢?面向对象原则指出,数据和数据上的操作应当绑定在一起,那么前者更好。 这是对面向对象的误解,面向对象设计的精髓在于封装,数据应当被尽可能地封装。 相比于成员函数,非成员函数提供了更好的封装,包的灵活性(更少的编译依赖),以及功能扩展性。
封装性
封装就是对外界隐藏的意思。如果数据被越好地封装,那么越少的东西可以看到它,我们便有更大的灵活性去改变它。这是封装带来的最大的好处:给我们改变一个东西的灵活性,这样的改变只会影响到有限的客户。作为粗粒度的估计,数据的封装性反比于可访问该数据的函数数量。这些函数包括成员函数、友元函数和友元类中的函数。 因此非成员非友元函数会比成员函数提供更好的封装, 我们应该选择
clearEverything()的第二种实现。
Item22提到,如果一个数据成员不是私有的,那么将会有无限数量的函数可访问它。
这里有两点值得注意:
友元函数和成员函数是一样的,因为友元函数也可以访问私有数据成员,它和成员函数对封装具有相同的影响。
非成员函数并不意味着它不可以是其他类的成员函数。尤其是在像Java,C#之类的语言中,函数必须定义在类中。
静态成员函数也是不错的选择。因为静态函数不能访问对象成员,因此不会影响对象的封装。
扩展性
在C++中,可以把这些非成员函数定义在相同的命名空间下。 但问题又来了:这些在命名空间下的函数并不在类中,它们会被传播到所有的源文件中。 而客户并不希望为了使用几个工具函数,就对这样一个庞大的命名空间产生编译依赖。 因此我们可以将不同类别的工具函数放在不同的头文件中,客户可以选择它想要的那部分功能:// file: webbrowser.h namespace WebBrowserStuff{ class WebBrowser{}; } // file: webbrowser-bookmarks.h namespace WebBrowserStuff{ ... } // file: webbrowser-cookies.h namespace WebBrowserStuff{ ... }
这也是C++标准库的组织方式,
std命名空间下的所有东西都被分在了不同的头文件中:
<vector>,
<algorithem>,
<memory>等。这样客户代码只对它引入的那部分功能产生编译依赖。
为了做到这一点,这些工具函数必须是非成员函数,因为类作为整体必须在一个文件中进行定义。
同一命名空间不同头文件的组织方式,也为客户扩展工具函数提供了可能。 客户可以在同一命名空间下定义他自己的工具函数, 这些函数便会和既有工具函数天然地集成在一起。 这也是成员函数无法做到的一个特性,因为类的定义对客户扩展是关闭的。 即使是子类也不能访问封装的(私有)成员数据, 况且有些类不是用来做基类的(见Item
7:将多态基类的析构函数声明为虚函数)。
除非注明,本博客文章均为原创,转载请以链接形式标明本文地址: http://harttle.com/2015/08/20/effective-cpp-23.html
相关文章推荐
- c++:sprintf应用实例
- C++泛型算法
- C++ 16进制转10进制
- C++ 分离编译 多个文件
- C++数组做参数
- c/c++的memset()函数
- 黑马程序员-C语言基础之结构体数组
- C语言指针使用总结
- effective c++ 确定对象被使用前已经被初始化
- Effective C++——条款29(第5章)
- c语言之数据拷贝memcpy strcpy
- 随手写了一段C++访问LDAP, 并且获取sid的代码
- C++ 栈和队列
- C++学习笔记(六)文件处理
- 详解C++编程中的变量相关知识
- C++项目中的extern "C" {}
- 指针变量作为函数参数使用时注意的问题!
- C++ Primer Plus (第6版)笔记心的---开始学习C++
- c语言字符数组与字符串的使用详解
- C++中export关键字的尴尬处境