四.设计与生命 18.让接口容易被正确使用,不易被误用 **举了个Day,Month,Year的例子进行简单的说明。在实际中就是要考虑客户如果使用你写的接口,怎么样能降低错误使用率,就像给别人提供API一样。要做到这一点还是很不容易的。 19.设计class 这个真的很难一开始就设计的很好,一般都是根据需求慢慢调整的,但是有一些公共特征还是考虑,比如构造和析构,需要什么操作等。 20.以pass-by-reference-const替换pass-by-value 主要考虑两点: ①效率问题,pass-by-value会导致很多临时对象的产生和销毁,就会多调用几次构造和析构,因此效率更低 ②对象切割问题,pass-by-value的方式将一个子类对象传入一个参数类型父类的函数,那么拷贝的对象将被切割成只有父类对象被保留。引用可以解决这个问题,因为引用本质上也是指针,就和多态是一样的 另外,内置类型,STL迭代器和函数对象一般采用pass-by-value,因为其复制代价很小
21.不要返回临时对象的引用 ①不要返回一个临时对象的引用 ②不要返回在堆上分配的对象的引用,因为这违背了new和delete成对出现的原则,这样的方式是很不合理的,稍加不注意就会导致内存泄漏问题。 ③也不要返回一个static对象的引用,因为static可能同时被很多地方需要,这样的话共享就存在问题。 所以对于这种问题,最好的解决方法就是不返回引用就OK了。 22.将成员变量声明为private 常规操作 23.宁以non-member、non-friend函数替换member函数 文中提到了封装性强弱的概念:一个类中的成员或者成员函数,被越少的代码访问,那么封装性越高。所以增加一个成员函数会增加访问成员变量的代码,因此降低了封装性。所以这个条款才如此建议。仁者见仁智者见智,这个条款目前楼主没有在实际项目中应用过,大部分还是以成员函数的方式实现,因此个人感觉这个条款并没有感觉有什么卵用! 24.若所有参数皆需类型转换,那么请采用non-member函数 文中举了一个有理数Rational运算的例子,在类中加入一个operator*(const Rational& other)的函数,可以实现类似 rational * 2的操作,其中2是个int,但是因为rational有一个以int为参数的构造,因此编译器帮你执行了隐式类型转换。但是反过来写2 * rational的时候,编译就报错了。因为2是个int,并没有operator*这个函数。但是为什么这样写就没有执行隐式类型转换呢?这又引出一个问题:隐士类型转换的合格条件是什么?答案是:必须是参数列中的参数才是隐士类型转换的有效参与者,类的执行者也就是'.'前面的那个对象(this指向的对象,比如说rational.func()中的rational是类执行者,相当于他是函数的调用人,地位较高,不能参与隐式类型转换),这就解释了为什么2放在前面是不行的。解决此种问题的方法是提供一个non-mem的operator*(Rational a, Rational b)即可。 25.写一个不抛出异常的swap函数 一般写swap最普通的方法就是利用中间变量,temp = a;a = b;b = temp,这种方法对于内置类型没任何问题,内置类型上的赋值绝对不会抛出异常,并且效率很高。但是如果a,b不是内置类型,就会调用类的copy构造函数和assign函数,并且必须是深拷贝。这样如果类的成员较多就会造成交换的效率很低,特别是针对pimpl实现方法,即成员中包含指针(即资源)时。更好的做法就是直接交换指针就可以了,相当于交换了两个int(指针都是4字节的),这就比拷贝这个指针指向的资源要快得多。 如何实现呢?只要将swap都转换成内置类型的swap就可以了,做法就是在类中提供一个public的swap(T& b)函数(T为一个类),将每个成员进行交换(如果成员中包含其他非内置对象,调用这个对象的swap函数即可)。然后提供一个non-member的swap(T& a, T& b)重载函数,在函数内部调用类中的a.swap(b),就可以像如下方式实现交换两个对象的操作:swap(a, b)。 注意: ①在类内的swap交换内置类型时要调用std命名空间内的swap函数,必须使用using std::swap,否则就变成递归函数了 ②另外文中说在std命名空间内不能加入新东西,比如重载swap函数,但是经博主测试是可以在std内重载swap函数的(g++版本为5.4.0)。
|