设计与声明

item18:make interfaces easy to use correctly and hard to use incorrectly.
对于所设计出的接口,我们要保证客户调用时能产生其期望的效果(重在逻辑上)才可以通过编译!否则不予编译过!

一、客户在调用接口时可能犯什么类型的错误?
1、接口多个参数且类型一样时(针对基本类型!!!),可能客户传递实参的顺序与接口希望获得的形参顺序不一致!(逻辑不对了,但是编译器也无法发现)
解决思路:包装形参类型为新的类型,这样客户在传递实参时必须使用构造函数传递形参,一旦次序不一致,编译器可以发现!
在这里插入图片描述
2、实参是个无效值
在类中以方法形式预定义出所有有效值,上一版中Month的构造函数为public,客户能在类外使用,但现在不可以了,客户端只能调用静态方法获取类对象,根本无法构造Month的对象,没有默认构造函数,一旦设计了构造函数,还是给了客户犯错的机会!
在这里插入图片描述
3、使用const规范操作的意图,防止客户误用自定义类型的返回值,可能想做比较操作,却误写成了赋值操作!我们需要让编译器发现这类错误并告知客户!
在这里插入图片描述

二、自定义类型与内置类型兼容
有利于设计接口方法

三、所设计的接口方法不应该期待用户对调用接口后,客户还需做些事情对接口操作进行善后,举个例子,返回的Investment*指向动态生成的空间,第一个函数需要客户释放内存空间,不是个好接口:
在这里插入图片描述
改进版本:迫使客户创建智能指针,当智能指针的生命周期结束后,其数据成员所指向的空间也会被释放。
在这里插入图片描述
希望为资源绑定删除器,默认采用delete释放空间,shared-ptr第二种构造函数写法如下,第二个参数为函数指针:
在这里插入图片描述

在这里插入图片描述item19:treat class design as type design
定义class等同于定义一个type,这里需要考虑:控制内存分配和释放、定义对象时的初始化和终结、重载函数和运算符等等。

type的设计规范:
1、新的type对象该如何被创建和释放=>class的构造函数、析构函数、new、delete用法
2、对象初始化和赋值操作间的差别 =>copy函数写法的不同
3、新type数据成员是否有合法值=>构造函数、setter、赋值写法
4、新的type是否属于某一继承体系=>重写虚函数或是声明为虚函数
5、新的type需要什么样的转化,显示?隐式?=>定义显示函数以供调用,单一参数的构造函数声明为explicit;声明一个运算符函数:operator 可转换类型()
6、新的type需要哪些函数和运算符
7、确定哪些方法需要不可在类外被访问=>确定私有成员
8、是否需要未声明接口
9、需要一个具体的type还是模板type呢?
10、有必要定义一个新的type吗?如果只是在已有类的基础上添加一些方法不做其他的改动,不如采用函数或是模板函数来代替新的派生类
在这里插入图片描述
item20:prefer pass-by-reference-to-const to pass-by-value
pass-by-value:调用copy constructor初始化形参,函数的返回值调用copy constructor返回至调用端。这就是为什么不提倡使用传值!导致很多次关于构造函数、析构函数的调用!函数调用浪费资源和时间,拖慢效率!
传址方式两大用途:
1、重在效率:pass-by-reference-to-const:添加const的方法表明,客户端不希望修改实参值(传值时,客户知道函数肯定是不会修改实参的值,不会有这方面的担心,为了明确告知客户,传址时函数也不会修改实参的值用const修饰),传址方式形参就是实参,底层传递就是对象的地址,不会有杂七杂八的构造、析构函数的调用!
2、重在继承体系下虚函数的使用:若采用基类的值传递方式,形参是基类,实参是派生类,实参将会被分割,只保留了基类部分赋值给基类形参,因此函数无法呈现多态性。
在这里插入图片描述在这里插入图片描述
传址和传值的选取?
对于内建类型、STL的迭代器和函数对象,通常选择pass-value更合适;此外,自定义的class最好还是采取pass-by-reference-to-const!
在这里插入图片描述
item21:don not try to return a reference when you must return an new object.
常见的错误:reference指向并不存在的对象,用到了reference,问问自己这个reference指向哪个对象(reference是现存对象的别名)?
1、返回值为局部变量(stack),客户端获得引用指向被释放的空间,带来未定义行为!
2、返回值为堆变量(heap-based),需要客户端进行delete(这本身就不是一种好设计,果断抛弃),但是当存在多次赋值操作时,无法获取到内部的引用,导致内存泄露!
在这里插入图片描述
3、返回值为local-static对象,有时候正确,有时候不正确,存在一些特殊的情况,可能带来一些隐蔽的问题!这些问题在后期调试过程中,将令人十分恼火!果断避免!
在这里插入图片描述
在这里插入图片描述
经过上面三种尝试,真的发现对与需要返回一个新对象的时候,牺牲效率也是没有办法的!只有返回对象值才能保证正确的程序行为。
在这里插入图片描述
在这里插入图片描述
item22:declare data members private
1、语法一致性:客户在使用类对象访问数据,不需要担忧是对象还是方法,因为我们数据永远不会出现在public接口中!
2、对数据成员进行更精准的控制,哪些数据只是可读?可读可写?只可写?将所有数据成员设置为私有后,就可以对各个数据成员设置不同访问控制权限。
3、封装性,不把数据进行封装,则客户可在自己的程序中使用这些数据,一旦对原始类所修改删除数据成员,客户程序的很多代码就废了。
在这里插入图片描述

item23:prefer non-member non-friend function to member function
一个方法可以由成员方法或者是非友元、非成员方法实现,选择非友元、非成员方法!
原因:
封装性:数据成员若是为public,毫无封装性可言;设置为private后,封装性有高低。若是可访问数据成员的方法愈多,封装性越低!封装性,主要是看修改代码时对其他程序的影响,因此,越少方法可访问到数据成员,设计者对数据成员掌控的程度就越高,封装性越好。这里的non-member function也可以是其他类的成员方法,只要不是member function或是friend function就好!

C++常用写法:将non-member non-friend和class定义放入到同一namespace中!
namespace具有跨越多个源码文件的能力,多个non-member non-friend之间很可能没有什么关联不需要互相依赖,将non-member non-friend函数的声明放在不同的头文件里,对于不同的客户来说,只需要根据自己的需求包含所需的头文件,此外,客户还可以扩展namespace(名称空间这里有点疑问??),向其中加入函数。

在这里插入图片描述
标准程序库:namespace std,有10+头文件,每个头文件都会向std namespace加入函数或是类实现逐步完善名称空间!因此,我们在实现名称空间里部分功能时只需要加入包含相应的头文件就好!(有点问题???后续看完再补充)

在这里插入图片描述item24:declare non-member function when type conversion should apply to all parameters.
以一个例子作为此item的解释,定义有理数类,现在需要为其添加算术运算,由于需要访问私有成员,所以non-member non-friend的实现是不可能的,那么该以成员函数还是非成员的友元函数形式出现呢?
在这里插入图片描述
若以成员函数实现,同类型运算不会出现问题:
在这里插入图片描述
但是当Rational和int类型运算时,第一个函数调用查找到类的成员方法后,发现参数类型不一致,但编译器发现可以通过调用构造函数来进行类型转化(隐式转换),因此编译通过。第二个函数调用会先查找成员方法,在查找非成员方法,均没有找到合适的函数故编译失败:
在这里插入图片描述
可发生隐式转化的两种条件:
1、类的定义中包含合适的构造函数(non-explicit)或是operator T成员函数
2、待转换的参数在函数参数列表中,如上面第二个函数调用中,this所指向对象不能发生隐式转化

解决之道:
将成员方法改变为non-member non-friend方法(不是友元好啊!),使得两个参数都可以发生隐式类型转化,以此支持不同类型操作数的运算!
在这里插入图片描述item25:consider support for a non-throwing swap.
标准程序库中的swap函数,涉及三次copy函数调用:
在这里插入图片描述
如果将以上的swap函数应用到如下的类中,交换Widget对象,将包含三次关于Widget copy函数调用,以及指向WidgetImpl对象的copy调用(书中讲的是交换所指向内容,我觉得只是交换指针值。。。不清楚的地方)。有必要交换Widget对象吗?没有!只包含一个指针!所以只是交换指向的内容,就可以实现两个对象间的交换!问题就是如何改写swap函数,使其不交换对象,只交换指针指向的内容。
在这里插入图片描述解决之道:特化std中的swap模板函数,使其关联到特定类型Widget上!凡是交换Widget类型对象,就调用具体化的swap模板函数,在这个模板函数中调用交换指针所指向内容的swap函数(可以在类中再实现一个交换指针指向内容的swap方法)。如下所示:
在这里插入图片描述注意:STL容器类中提供一个public的swap方法,以及在std特例化的swap函数(调用前者),提高效率。

如果是class包含一个指针成员,希望交换对象的时候仅仅交换所指向的内容,上述实现可以满足。但是如果是template class,那么在具体化std中的swap时模板时,由于class 也是个template,模板函数不支持偏特化(template class导致的),因此想要具体化模板函数swap的希望破灭了。虽然模板函数不支持偏特化,但我们可以重载模板函数代替偏特化,使得Widget具体对象能调用到这个更具体一些的swap函数。可是std中不允许添加新的class template和function template。问题就出在名称空间std上!不要用std,将Widget template class,WidgetImpl template class,non-member函数,放到一个自定义的名称空间!
在这里插入图片描述修改版本:
在这里插入图片描述
为了让特化的swap能被广泛的使用到,应该在std中特化一个swap模板函数并在类定义的名称空间中定义一个non-member的swap函数。这是针对一些客户习惯使用std ::function,如果不在std中进行特化(能特化时)而只是在名称空间定义一份swap的重载函数,将使得具体化的swap函数无法被调用到!

客户正确使用swap函数写法:
在这里插入图片描述客户在使用swap函数时,编译器如何确定最佳的swap函数?
(名称空间有点迷。。。还需要再看看名称空间)
在这里插入图片描述swap函数设计理念:
首先,所设计class或是template class使用默认的swap函数不存在效率问题,可以不再设计swap函数,让客户用std::swap()即可!
在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值