Effective C++ (笔记) : 条款18 -- 条款23

条款18:让接口容易被正确使用,不易被误用

  1. 在(参数)类型上客户不知道怎么使用的时候,可以导入简单的“外覆”类型来区别参数。也就是,自定义数据类型,使客户明确调用相关的类型,防止误用。
  2. 尽量让自定义类型的行为和内置类型的行为相同,因为客户会想当然的和使用内置类型一样使用自定义类型,这也就是上面说的让接口容易被正确的使用。STL容器的接口十分一致,这也是他们非常容易使用的一个原因。
  3. 任何接口如果要求客户必须记得做某些事情,那么就有着“不正确的使用”的倾向,因为客户可能会忘记做那件事情。

促进正确使用的办法包括接口的一致性和内置类型的兼容性;阻止误用的办法有建立新类型,限制类型上的操作,束缚对象值以及消除客户的资源管理责任。

条款19:设计 class 犹如设计 type

  • 新 type 的对象应该如何被创建和销毁?
  • 对象的初始化和对象的赋值有什么差别?
  • type的对象如果以值传递,意味着什么?
  • 什么时新type的合法值?
  • 你的新type需要配合某个继承图系吗?
  • 你的新type需要什么样的转换?
  • 什么样的操作符和函数对此type是合法的?
  • 什么样的标准函数应该被驳回?
  • 谁该取用新type的成员?
  • 什么时新type的“未声明接口”?
  • 你的新type有多么一般化?
  • 你真的需要一个新type吗?

条款20:宁以 pass-by-reference-to-const 替换 pass-by-value

pass-by-value在对象的构建和销毁的时候,需要多次调用构造函数和析构函数,是一个昂贵(费时)的操作;pass-by-reference-to-const效率要高的多:没有任何构造函数或析构函数被调用,因为没有任何新对象被创建。reference能够消除对象的创建销毁过程和切割问题(参数是基类型,如果不使用引用,那么会引起切割),const的作用是让函数不能修改对象的值。

如果窥视C++编译器的底层,你会发现,引用往往是以指针的形式实现出来,因此,传递引用往往意味着真正传递的是指针。如果对象属于内置类型,pass-by-value往往比pass-by-reference效率高一些。

条款21:必须返回对象时,别妄想返回其reference

任何时候看到一个reference声明式,都应该立刻问自己,它的另一个名称是什么?因为它一定是某物的另一个名称。

函数创建新对象的途径有两个:heap(堆) 和 stack(栈)
中。如果定义一个local对象,那就是在栈空间创建。当函数返回的时候,栈空间将会释放,但是返回的却是析构了的对象的引用,接下来,问题多多的。你很聪明,你会想到在堆中创建对象,这样函数返回也不会析构这个对象,这确实是可以的,如此,将会产生另一个问题:谁该对被new出来的对象实施delete?
即使调用者诚实谨慎,当发生下面情况的时候,也就不能阻止内存泄露:

Rational w, x, y, z;
w = x*y*z;

同意个语句调用两次函数(*),两次使用new,释放两次才可以。但却没有合适的方法让他们取得函数返回的引用,也就不能实施释放,这绝对会导致资源泄露。

你可能还很聪明,让对象定义为static,这样自我感觉还不错,但是,问题还是来了。前一次计算的结果会累计,并影响下一次的计算。这显然也不是我们希望发生的。

解决的方法也不难想到:一个必须返回对象的函数就让那个函数返回新对象吧。当然,这会引起对象构造和析构的成本,然而,长远来看,那只是为了获取正确行为而付出的小小代价。即使需要优化,那也是编译器作者的事情。

条款22:将成员变量声明为 private

  1. 如果成员变量不是public,那么客户访问变量的唯一方式就是使用成员函数,也就是说,对成员变量的所有访问都是通过括号的形式访问的,也达到了接口一致性的要求。
  2. 可以使用函数让你对成员变量的处理有更精确的控制。可以实现只读或者变量正确性的控制。
  3. 封装。如果使用函数访问成员变量,日后可以修改某个计算替换这个成员变量,而class客户一点都不会知道类的内部发生了变化。 - 举个例子:我们要计算某汽车多次测试的平均速度,通过一个函数返回这个值,两种实现方式:用一个变量存储这个均值,查询的时候只要返回这个值即可;每次函数调用的时候重新计算均值。

上述第一种做法 - 利:查询速度快,弊:大量空间存储任意时刻的速度均值;第二种做法:查询速度慢,但是需要的内存空间小。

哪一个方案更好呢?情况不同,答案不同。所以,将成员变量隐藏在函数接口的后面,可以为“所有可能的实现”提供弹性。

Protected成员变量和Public一样缺乏封装性,因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。

条款23:宁以 non-member non-frind替换 member 函数

一个观点:成员函数的封装性比非成员函数的封装性要低。

越多的东西被封装,越少人可以看见它;越少人可以看见它,我们就有越大的弹性去改变它 --- 越多东西被封装,我们改变那些东西的能力就越大。 --- 封装:使我们能够改变事物而只影响有限客户。

我们量化一下:使用能够访问数据的函数数量作为封装性的一种粗糙量化。越多函数能够访问它,数据的封装性就越低,反之亦然。

能够访问私有数据成员的函数只有成员函数和友元函数,如果在member函数,non-member
non-friend
函数和non-member函数之间做选择,non-member
non-friend
函数,因为它不增加“能够访问私有数据的函数”数量。 对吧,因为它的封装性更高,这也是我们想要的。

我们将上述函数称之为“便利函数”,因为它是通过使用共有成员函数完成一些成组或者组合的操作。将所有“便利函数”放在多个头文件中但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。 --- 就像C++标准库所做的那样。

时间: 2024-10-06 20:51:46

Effective C++ (笔记) : 条款18 -- 条款23的相关文章

Effective C++:条款18:让接口容易被正确使用,不易被误用

(一) 看下面这个例子: class Date { public: Date(int month, int day, int year); }; 很有可能引起下面这两个错误: (1)他们也许会以错误的次序传递参数,如:Date d(30, 3, 1995); (2)他们可能传递一个无效的月份或天数,如:Date d(2, 30, 1995); 许多像这类客户端错误. 解决方法:简单的外覆(wrapper types)类型来区别天数.月份.和年份,然后于Date构造函数中使用这些类型: struc

effective c++ 条款18 make interface easy to use correctly and hard to use incorrectly

举一个容易犯错的例子 class Date { private: int month; int day; int year; public: Date(int month,int day,int year) { this->month = month; ... } } //wrong example Date date(30,3,1995);//should be 3,30 Date date(2,30,1995);//should be 3,30 使用类型可避免这个问题 class Month

《Effective C++》资源管理:条款22-条款24

条款22:将成员变量声明为private 讲解这个条款的思路是,首先看看成员变量为什么不应该是public,这个观点同样适用于protected成员变量,最后得出结论:成员变量应该是private. 首先从语法一致性开始(条款18),如果成员变量不是public,那么客户访问该成员的唯一方法就是通过成员函数(如果没有友函数).如果public接口内的每样东西都是函数,客户在使用这个对象时,就不需要疑问到底是访问变量还是函数了,因为这个时候不能访问成员变量. 或许一致性不是令你信服的理由.还有一个

《Effective C++》资源管理:条款13-条款15

在系统中,资源是有限的,一旦用完必须归还给系统,否则可能会造成资源耗尽或其他问题.例如,动态分配的内存如果用完不释放会造成内存泄漏. 这里说的资源不仅仅是指内存,还包括其他,例如文件描述符.网络连接.数据库连接.互斥锁等. 在任何情况下都要把不使用的资源归还系统是一件非常困难的事情.尤其是考虑到异常.函数内多重回传路径等. 基于对象的资源管理办法几乎可以消除资源管理的问题.下面介绍的方法也可以弥补一些不足. 条款13:以对象管理资源 现在假设用一个类来模拟投资行为,各种投资类都继承自一个基类In

C++基础和STL,Effective C++笔记

C++基础 static static变量存储在静态数据区 相对于function:在函数内,变量,内存只被分配一次,多次调用值相同 相对于其他模块(.c文件):变量和函数,不能被模块外其他函数访问(private) 相对于类:类中的static变量和函数属于整个类,而不是对象 全局变量 VS 全局静态变量 若程序由一个源文件构成时,全局变量与全局静态变量没有区别. 若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该

Effective C++笔记04:设计与声明

条款18:让接口容易被正确使用,不易被误用 1,好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2,"促进正使用"的办法包括接口的一致性,以及与内置类型的行为兼容. 3,"阻止误用"的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. 4,shared_ptr支持定制型删除器.这可以防范DLL问题,可以用来自动解除互斥锁. 条款19:设计class犹如设计type 博客地址:http://blog.csd

Effective C++笔记:构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

Effective C++笔记06:继承与面向对象设计

关于OOP 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtual或non-virtual或pure-virtual. 3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为

Effective C++笔记05:实现

条款26:尽可能延后变量定义式的出现时间 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 有些对象,你可能过早的定义它,而在代码执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本. 所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止. 这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为. 遇到循环怎么办?此时往往我