《Effective C++》学习总结(条款16- 20)

条款16:成对使用 new 和 delete 时要采取相同形式

1.new:当使用new的时候,会发生如下两件事:
  • 首先,一块内存会被分配出来;
  • 其次,针对此内存会有一个(或更多)构造函数被调用。
2.delete:当使用delete的时候,会发生如下两件事:
  • 首先,针对此内存会有一个(或更多)析构函数被调用;
  • 其次,内存被释放(通过operator delete的函数)。
3.那么问题在于:即将被删除的那个指针,所指的是单一对象还是对象数组?
  • 也就是:即将被删除的内存之内究竟存有多少对象?

  • 而且:数组所用的内存还包括“数组大小”的记录,以便delete知道需要调用多少析构函数。而单一对象的内存则没有这笔记录。

4.请记住:
  • 如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ];
  • 如果你在new表达式中没有使用[ ],一定不要在相应的delete表达式中使用[ ]。

 

条款17: 以独立语句将 newed 对象置入智能指针

1.在一个语句中编译器拥有某种程度上重新排列操作的自由
  • 编译器没有那么智能,所以在“资源被创建”(例子中的new Widget)和“资源被转换为资源管理对象”(例子中的显式casting)两个时间点之间有可能发生异常干扰,从而导致返回的资源创建时返回的指针遗失,资源由此发生泄漏。
2.避免这类问题的方法很简单:使用分离的独立语句
  • 在单独语句内以智能指针存储newed所得对象
  • 再在单独语句中进行调用,这个动作绝不至于产生泄漏
  • 以上之所以行的通,因为编译器对于”跨越语句的各项操作“没有重新排列的自由
3.请记住:
  • 以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏

 

第四章:设计与声明

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

1.明智而审慎地导入新类型对预防“接口被误用”有神奇疗效(以一个表现日期的class设计为例)
2.新类型导入定位成功后,限制每个类型对应的取值范围是第二个问题
  • 然而使用枚举enums限制取值并不安全(因为enum hack的存在——条款02中说明过,enum可被拿来当一个ints使用)
  • 比较安全的解决方法是预先定义所有有效的取值——这个取值是通过函数返回的,“以函数替换对象,表现某个特定月份(例子)”原因在于 non-local static对象的初始化次序有可能出问题(见条款04)
3.预防错误的另一个办法是,限制类型内什么事可做,什么事不能做
  • 常见的限制是加上const(见条款03中以const修饰operator*的返回类型)
4.一般性准则:除非有好理由,否则应该尽量令你的types的行为与内置types一致——内置类型行为的一致性
  • 一旦怀疑,就拿int做范本
5.使用shared_ptr的最大好处——自带deleter,并可以定义一个函数作为其删除器
  • 同时这个deleter还带有追踪绑定的特性,不需要担心对象在动态连接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁(cross-DLL problem)
6.请记住:
  • 好的接口很容易被正确使用,不易被误用。你应该在你的所有接口中努力达成这些性质
  • “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容
  • “阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
  • shared_ptr支持定制性删除器(custom deleter),这可以防范DLL问题,可被用来自动解除互斥锁(mutexes;见条款14)等等

 

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

1.一定要明白设计class的重要性
class侧重于自定义的类,而type侧重于系统预定义的类(像int、double、string、vector)设计好的class,使之像设计type一样,就是说要使自己设计的类像系统预定义的类那样好用,这对设计思想提出了较高的要求。
  • 新type的对象应该如何被创建和销毁? 这会影响到class的构造函数和析构函数以及内存分配函数和释放函数(operator newoperator deleteoperator new[]operator delete[]——见第八章)的设计,当然前提是如果打算撰写它们;

  • 对象的初始化和对象的赋值该有什么样的差别? 这个答案决定你的构造函数和赋值(assignment)操作符的行为,以及其间的差异。很重要的是别混淆了“初始化”和“赋值”,因为它们对应于不同的函数调用;(见条款4)

    • 解读:深度拷贝与浅拷贝——当用一个对象去初始化另一个对象的时候,就会去调用复制构造函数。举个例子:
      Class a = Class(2,1);
      Class b = a;
      其中的b 对象就是调用了复制构造函数后生成的对象。

      当函数按值传递给对象或者函数返回对象的时候,都会使用复制构造函数。

      默认复制构造函数是逐个复制非静态成员的值,因为静态成员是独立于每个对象而存在的。这种复制称为浅复制。

      例如,一个String类型对象指向一片内存,那么使用默认复制构造函数,将把这个地址赋给新值。使两个指针指向同一个对象。这样当调用析构函数,释放对象时将会出错,试图去释放以释放的内存会报错。

      这时候就需要深度复制了。将去复制指向的值。而不是指针值。也就是重新去调用new操作符生成一个新的成员——开辟了一块新的内存空间来储存深度拷贝后的副本

      二者最大的区别浅拷贝和深拷贝主要区别就是复制指针时是否重新创建内存空间。如果没有没有创建内存只赋值地址为浅拷贝,创建新内存把值全部拷贝一份就是深拷贝;(默认构造函数是浅拷贝,使用new开辟新内存空间对应深拷贝)

    • 重载的=操作符和复制构造函数的关系:其实,=操作符是在两个已经创建的对象之间的操作,而复制构造函数就是,这个对象原来没有,去初始化这个对象才会调用的;

  • 新type的对象如果被passed by value(以值传递),意味着什么? 记住,copy构造函数用来定义一个type的pass-by-value该如何实现;

    • **解读:会调用相应的拷贝构造函数,要注意是否需要深度拷贝
  • 什么是新type的“合法值”? 对class的成员变量而言,通常只有某些数值集是有效的。那些数值集决定了你的class必须维护的约束条件,也就是决定你的成员函数(特别是构造函数、赋值操作符和所谓“setter”函数)必须进行的错误检查工作。它也影响函数抛出的异常、以及(极少被使用的)函数异常明细列;

    • 解读:要对对象的合法值进行限定,通常在构造函数中对成员变量的范围给出限定,警惕不安全的输入值!
    • 返回函数是较好的选择(见条款18),可以防止non-local static 在调用前未初始化的问题
  • 你的新type需要配合某个继承图系(inheritance graph)吗? 如果你继承自某些即既有的class,你就受到那些class的设计的束缚,特别是受到“它们的函数是virtual或non-virtual”的影响。如果你允许其他class继承你的class,那么会影响你所声明的函数——尤其是析构函数——是否为virtual;(见条款7)

    • 解读: 就是判断自己设计的class是否需要继承或被继承,是基类还是派生类。如果是基类的话,要注意是否允许这个基类生成对象(是否需要利用纯虚函数设计成抽象类),以及要将析构函数前面加上virtual。
  • 你的新type需要什么样的转换? 你的type生存于其他types之间,因而彼此该有转换行为吗?如果你希望允许类型T1之物被隐式转换为类型T2之物,就必须在class T1内写一个类型转换函数(operator T2)或在class T2内写一个non-explicit-one-argument(可被单一实参调用)的构造函数。如果你只允许explicit构造函数存在,就得写出专门负责执行转换的函数,且不得为类型转换操作符(type conversion operators)或non-explicit-one-argument构造函数;

    • 解读: 主要是针对隐式转换,operator OtherType() const,但通常情况下隐式转换也意味着隐患,所以设计时要谨慎。另外,构造函数中也要当心,如果不想让隐式构造发生,就要在前面加上explicit关键字。举个例子:

      class A{
      private:
         int a;
      public:
      	 A(int b):a(b){}
      };
      void fun(A obj);
      
    • 若调用fun(3),则编译器也能接受,因为编译器自动作了fun(A(3))的处理,这就是隐式构造。而如果用户自己写fun(A(3)),这是显式构造。当A的构造函数前有explicit时,fun(3)的调用将通不过编译器。通常情况下,隐式转换是安全的;

    • RAII class内部的casting需要格外注意(显示与隐式),涉及到对应原始资源的管理与访问;(见条款15)

  • 什么样的操作符和函数对此新type而言是合理的? 这个问题的答案决定你将为你的class声明哪些函数。其中某些该是member函数,某些则不是;

    • 解读:就是设计什么样的成员函数,以及重载哪些运算符
  • 什么样的标准函数应该驳回? 那些正是你必须声明为private者;(条款6)

    • 解读: 是说哪些函数对外公开——都能调用,哪些函数对内使用——以阻止调用,这就是private,public和protected的功能,protected只有在有继承关系的类中使用才能发挥它真正的力量,普通的类用private和public就足够了;
  • 谁该取用新type的成员? 这个提问可以帮助你决定哪个成员为public,哪个为protected,哪个为private。它也帮助你决定哪一个class和/或function应该是friend,以及将它们嵌套于另一个之内是否合理;

    • 解读: 就是类的封装性问题,一些而言,成员变量都应该是private的,而在public函数里面提供对这些成员变量的访问get和set函数。如果需要的话,可以使用友元,但友元也要慎用,因为有些编译器对之支持的不是很好。但并不代表友元是破坏封装性的特性,更应该将其视为是一个特殊的访问接口;
  • 什么是新type的“未声明接口”? 它对效率、异常安全性(见条款29)以及资源运用(例如多任务锁定和动态内存)提供何种保证?你在这些方面提供的保证将为你的class实现代码加上相应的约束条件;

    • 解读: 实现接口是通过派生类实现的,每个派生类依据自身特点,可以获取同一接口的不同实现 ,也就是所谓的多态;
    • 通过使用异常安全函数提供异常安全型的保证(三个保证——条款29)
    • 通过使用RAII class进行对象资源管理(如锁的自动unlock——条款14中将unlock函数放在析构函数中,动态内存——条款15中的两种转换类型的方式)
  • 你的新type有多么一般化? 或许你其实并非定义一个新type,而是定义一整个types家族。果真如此你就不该定义一个新class,而是应该定义一个新的class template;

    • 解读: 牵涉到泛型编程,就是模板的概念,这是C++比C要方便的多的多的地方——它将需要的类型交给编译器自动生成;
  • 你真的需要一个新type吗? 如果只是定义新的derived class以便为既有的class添加机能,那么说不定单纯定义一或多个non-member函数或template,更能够达到目标;

    • 解读: 如非必要,不要设计太多冗余的类。
2.请记住:
  • class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过这个条款内覆盖的所有讨论主题;
  • 定义高效的类是有挑战性的。在C++中用户自定义类生成的类型最好可以和内建类型一样好。

 

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

1.pass-by-value的方式是通过调用copy构造函数来获得实参的副本,而这样会导致相应copy构造函数与析构函数的多次调用(尤其是在derived class中)
2.pass-by-reference-to-const的效率要高很多,没有任何构造函数或析构函数被调用
  • 其核心在于:const这个限定符,保证传入的参数不被做出任何的改变
3.pass-by-refernece能够避免slicing(对象切割)问题
4.当需要传递内置对象类型的参数时,使用pass-by-value会有更高的效率
  • C++定义了一套包括算术类型和空类型在内的基本数据类型,算数类型包括:字符型,整型,bool型,和浮点型。空类型,即void类型
5.小型的用户自定义类型不必然成为pass-by-value优良候选人——要根据实际情况来判断
  • 一般情况下,pass-by-value不昂贵的唯一对象就是内置类型和STL的迭代器和函数对象
6.请记住:
  • 尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并且可以避免对象切割问题
  • 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值