Effective C++ 18-23

18.争取使类的接口完整并且最小。

类的用户接口是指使用这个类的程序员所能访问得到的接口,典型的接口里只有函数存在,封装好类的数据成员。

完整是指接口中包含所有 合理的操作的函数。最小是指函数尽可能少且功能不重复。

接口中的函数要少的原因:接口中函数越多,越让其他人难以理解,函数多了会让人混淆。函数多了难以维护,更难维护与升级。长的类定义会导致长的头文件,浪费大量编译时间。

19.分清成员函数,非成员函数和友元函数

成员函数可以是虚函数,即可以实现动态绑定,而非成员函数不行。

关于类一些操作符重载的思考

如 operator+ 进行加法的重载,假设在有理数 Rational 类中有成员函数 +法的操作,则对于一下三个运算:

	result = R1 + R2;
	result = R1 + 2;
	result = 2 + R1;

先补充一个前提,Rational的构造函数的声明为Rational(int  numerator = 0,int denominator = 1)。则对于第一行的运算,就是为 R1.operator+(R2),即调用R1中的成员函数,而对于第二行的运算也是正确的,这里将2隐式转换称了Rational类(编译器会对每个函数的每个参数执行隐式类型转换),在没有声明explicit的情况下,即这行编译器是如此解释的 R1.operator+( Rational(2))。而对于第三行的运算是出错的,因为编译器是如此理解这行的操作
2.operator+(R1),在对于整数2找不到对应的operator+函数后,这里并不会隐式将2转换为有理数。编译器又去寻找非成员的全局的operator+( 2,R1),结果也没有找到,最后搜索失败。

而当将operator+ 作为友元函数时,对于第三行,编译器就如此理解 operator+( 2,R1),2 作为函数的参数,编译器也会尝试隐式转换为有理数类。

但是尽量不要使用友元函数,而使用全局函数,最好能够调用类中的共有接口来实现操作符运算。因为使用友元函数会带来很多麻烦,简单点来说,类是为了封装的,友元会降低类的封装性。

20.避免public接口出现数据成员。

简单来说,public中只有函数,对数据成员的读写只通过函数来实现,这样,可以通过设置函数来实现数据成员的 不可读写,只读,只写,可读写。

复杂一点,实现功能分离 functional abstraction ,如果使用函数来实现对数据成员的访问,当更改对数据成员的设定时,只要在这些函数中进行一些修改,添加一些代码,就可以实现效果,且对用户隐藏这些细节。

21.尽可能使用const。

const除了声明常量外,在类中常用于使实参为常量即声明参数为const,以及使函数不改变类内成员的值即声明函数为const,以及让函数的返回值为常量。

让函数的返回值为常量,减少用户出错的几率。如对operator+的重载,如果返回值不是一个常量,对于这个式子 (a+b) = c,就容易阻止这种无聊的错误。

声明函数为const,其实是指函数重载,为const对象调用,而若一个函数只有const的版本,就会发生将普通对象转换为 const对象,而对于const函数中对const的对象的操作,不能改变const对象中一般数据成员的值 即const函数不能改变对象的成员的值。 但是const函数中对数据成员不进行改变又不是绝对的,如果有指针的数据成员,在函数中不改变指针,但可以去改变指针指向的内容的值, 这样的函数也可以通过编译器的检测,但这和我们的为const的定义相违背。另一种conceptual constness
的观点认为const成员函数可以修改它所在对象的一些数据,在用户不发觉的情况下,使用关键字mutable。或则可以强制一点,使用const_cast声明一个局部的this指针,然后强制修改这个指针指向的内容的值,这样是可以通过的。

22.尽量传引用,不要传值。

对于传值,函数的形参是通过实参的拷贝来初始化,函数的调用者是函数返回值的拷贝。通过值来传递一个对象,具体含义是由这个对象的类的拷贝构造函数定义的。而这样既浪费空间,又浪费时间。

使用传引用,没有新的对象创建,传递的一直都是引用,则没有构造函数和析构函数的调用。

传引用的另一个优点是 避免了 切割问题 slicing problem。 当一个派生类的对象被当作基类的对象进行传递时,派生类对象会由 基类的构造函数使用 派生类对象转换得到的基类对象 作为参数 赋值一个新的 完整的基类对象,其派生类中所具有的行为特性会被切割掉,只是一个简单的基类对象。而如果使用了传引用,传给函数中使用的依然是这个对象本身,而这个对象有多态,有派生类的功能。

对于传引用。一般都是使用指针来实现的,而对于一些较小的对象,如int,传值其实会比传引用更加高效。

23.必须返回一个对象时,不要试图返回一个引用。

传引用的一个严重的错误,传递一个并不存在的对象的引用。举例,对于操作符重载中的 operator= , 其返回值为 const T,返回对象的原因:

如果返回为引用,则这个别名的原名是谁,在哪里?实现对于+ ,其返回的对象不是两个参数,而是在函数中新建的对象,而这个对象不能在栈中,因为其在函数结束后就会被释放,而这个别名指向一块已经释放的内存这是不正确的。这个对象也不能在堆中,对象不能是动态建立的,因为在调用+后,并没有储存这个别名并释放这个别名所在的内存,这是内存泄漏。所以,不能返回一个引用,所以必须返回一个对象,即使这个对象返回到调用处要 先在函数中进行一次构造和析构,再在调用出进行一次构造,在使用后又要调用一次析构函数,即使花费不低,但只能使用返回对象。

时间: 2024-10-11 11:40:41

Effective C++ 18-23的相关文章

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 JavaScript Item 23 永远不要修改arguments对象

本系列作为Effective JavaScript的读书笔记. arguments对象只是一个类似数组的对象,但是它并没有数组对象提供的方法,比如shift,push等.因此调用诸如:arguments.shift(),arguments.push()是错误的. 在Item 20和Item 21中,知道了函数对象上存在call和apply方法,那么是不是可以利用它们来让arguments也能够利用数组的方法呢: function callMethod(obj, method) { var shi

More Effective C++----(18)分期摊还期望的计算

Item M18:分期摊还期望的计算 在条款M17中,我极力称赞懒惰的优点,尽可能地拖延时间,并且我解释说懒惰如何提高程序的运行效率.在这个条款里我将采用一种不同的态度.这里将不存在懒惰.我鼓励你让程序做的事情比被要求的还要多,通过这种方式来提高软件的性能.这个条款的核心就是over-eager evaluation(过度热情计算法):在要求你做某些事情以前就完成它们.例如下面这个模板类,用来表示放有大量数字型数据的一个集合: template<class NumericalType> cla

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

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

Effective C++ 条款23

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

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

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

More Effective C++

条款一:指针与引用的区别 指针与引用看上去完全不同(指针用操作符'*'和'->',引用使用操作符'.'),但是它们似乎有相同的功能.指针与引用都是让你间接引用其他对象.你如何决定在什么时候使用指针,在什么时候使用引用呢? 首先,要认识到在任何情况下都不能用指向空值的引用.一个引用必须总是指向某些对象.因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量.相反,如果变量肯定指向一个对象,例如你的设计不允许变量为

Maven:父POM文件,Effective POM for project

<?xml version="1.0" encoding="GBK"?> <!-- ====================================================================== --> <!-- --> <!-- Generated by Maven Help Plugin on 2019-02-02T21:18:23+08:00 --> <!-- See: htt

More Effective C++----(20)协助完成返回值优化

Item M20:协助完成返回值优化 一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数(参见条款M19),这种调用是不能避免的.问题很简单:一个函数要么为了保证正确的行为而返回对象要么就不这么做.如果它返回了对象,就没有办法摆脱被返回的对象.就说到这. 考虑rational(有理数)类的成员函数operator*:(返回类型为const是为了防止连续对操作符的操作,一是返回的对象是一个临时对象,多个操作是在其临时对象上的操作,二是不符合内置类型的要求) class

More Effective C++----(19)理解临时对象的来源

Item M19:理解临时对象的来源 当程序员之间进行交谈时,他们经常把仅仅需要一小段时间的变量称为临时变量.例如在下面这段swap(交换)例程里: template<class T> void swap(T& object1, T& object2) { T temp = object1; object1 = object2; object2 = temp; } 通常把temp叫做临时变量.不过就C++而言,temp根本不是临时变量,它只是一个函数的局部对象.(一切事物皆对象