effective cpp

const

1.我们需要尽可能的在可以加const的地方上const

  • const可以接受左值和右值
  • const可以防止对应的值被意外的修改
  • 我们很多时候需要的是概念上的常量性,即不能通过任何方式更改一个对象本身所拥有的内容。而不是编译器所指的bitwise
    2.使用const和enum替换掉#define
  • #define对应的值是不会出现在记号表里面的,然而const对应 值是会出现的
  • 单纯 define替换可能会出大麻烦,因为他不能去地址,它也不能在debug模式里面去追踪

reference

1.使用const typename & x,这么做可以保证不对需要传入的对象进行复制,达到减少程序运行时间的目的
2.在必须返回对象的情况下,不要返回对应的reference,因为这样会导致出错

3.在返回对象的情况下,不要使用指针/引用对这个对象取地址,因为其返回的是一个临时对象。使用指针/引用指向它会造成空悬指针

class基本

class里面的函数总概论

构造函数

  • 构造函数的创建:对于一个没有构造函数的对象来说,编译器会为他自动合成一个。
  • 构造函数的调用:只有在他们必须要调用的时候才会创建他们,如果不被调用就不会创建。如果类有自己的构造函数了,那么编译器就不会合成了

拷贝构造函数

  • **拷贝构造函数的创建:**对于一个没有构造函数的对象来说,编译器会为他自动合成一个
  • **拷贝构造函数的调用:**只有在他们必须要调用的时候才会创建他们,如果不被调用就不会创建。如果类有自己的构造函数了,那么编译器就不会合成了

析构函数

  • 析构函数的创建:对于一个没有析构函数的对象来说,编译器会为他自动合成一个。
  • 析构函数的调用:只有在他们必须要调用的时候才会创建他们,如果不被调用就不会创建。如果类有自己的析构函数了,那么编译器就不会合成了

拷贝赋值函数

  • 拷贝赋值函数的创建:对于一个没有拷贝赋值的函数对象来说,只有当代码显式的用到operator的时候。编译器才会为他合成并且使用。如果不调用,编译器不会进行合成和调用
  • 拷贝赋值函数的调用:只有当代码显式的用到operator的时候。编译器才会为他合成并且使用。

移动赋值函数/移动构造函数

  • 移动赋值函数/移动构造函数:对于这两个,只有显式定义,否则编译器不会自己合成

重载与虚函数

以上这些函数,可重载的有

  • 除了析构函数。其他的都可以重载

可设置为虚函数的有

  • 拷贝赋值函数:可以但是没必要,因为会造成误解
  • 移动赋值函数:可以但是没必要,因为会造成误解
  • 析构函数:
    • 在有virtual函数的时候可以,而且可以完整的析构全部的对象,避免出现意外
    • 在没有virtual函数的时候这就不是一个好主意了,因为多出来的vptr不但让对象本身变大了,而且让程序变慢,没有这个必要

函数里面可以调用虚函数的有

  • 除了析构和构造:因为在析构和构造的时候,对象本身是不完整的。如果调用就会出现一些错误的赋值/删除,导致错误

五种基本函数的使用

构造函数:

  • 构造函数与虚函数:间重载与虚函数
  • 如果不需要的话,需要使用delete做一个明显的拒绝

拷贝构造,移动构造:

  • 如果不需要的话,需要使用delete做一个明显的拒绝

拷贝赋值

  • 首先,拷贝赋值函数一定要返回一个reference to *this,因为只有这样才能实现连锁赋值,确保自己的操作与大方向一致
  • 在拷贝赋值函数中,一定要处理自我赋值,可以使用的技术通常就是最后拷贝一个副本在进行复制,或者使用swap技术
  • 如果不需要的话,需要使用delete做一个明显的拒绝

移动赋值

  • 如果不需要的话,需要使用delete做一个明显的拒绝

析构函数

  • 我们不能让异常跑出析构函数:一旦跑出析构函数,就可能导致资源的泄露,比较好的方法是使用share-ptr或者unique-ptr,保证析构函数不跑出对象
  • 如果不需要的话,需要使用delete做一个明显的拒绝

对象与资源

管理资源

  • 以对象(类)的方式管理资源,使用share-ptr,unique-ptr,可以防止资源泄露。
  • 资源管理类不应该可以复制,复制的话可能导致资源泄露
  • 资源管理类里面应该提供原始资源的访问形式。

初始化对象

  • 在使用对象之前也要确保对象已经初始化。保证不会出现一些奇奇怪怪的值。其中一个合适的方法就是使用初始化队列
  • 设计每一个class就像设计一个type,创建,销毁,转换,对应的接口,函数,异常都要考虑。这种type其中一个好的作用就是让接口被正确的使用
  • 延后对象出现的时间,减少不必要的资源占用
  • 成员变量需要设置为private的。保证符合封装的逻辑概念
  • 对于成员函数,如果是定义在类的内部,那么他就是要给inline函数,构造函数和析构函数,virtual函数一般不会是inline的,inline会增加成员体积,且需要重新编译

复制对象

  • 在复制对象的时候,我们应该复制它的每一个资源,都不能保证漏掉
  • 复制对象的时候也应该考虑自赋值问题,以及其对应的解决方案
  • 时刻记住类里面的函数是有this指针的。这个指针自己是不能做隐式类型转换的,如果需要做隐式类型转换,我们应该使用一个non-member函数,至于是否需要使用non-friend,我们需要看这个函数内部是否应该访问对象内部private的资源
  • 对象与对象之间少做转型。转型会带来不必要的麻烦

析构对象

  • 参考重载与虚函数,五种基本函数的使用的析构函数部分

对象与对象

对于单一继承:

  • private:慎用private继承,尽可能使用复合继承,必要的时候才需要使用private继承

  • public:

    • 确保public继承继承出来的关系是:class a继承与class b: a包含b的成员变量,但a和b是两个不一样的对象
    • 保证不去更改继承而来的non-virtual函数,因为一旦更改会违背第一条
    • 保证不更改继承而来的函数的缺省值,因为一旦更改会违背第一条

对于多重继承:

  • 谨慎与小心的使用它是我们的主旋律,因为他可能会导致重名等一系列问题。

对于继承

  • 区分接口继承,实现继承
    • 接口继承:当你使用纯virtual继承的时候,你就是继承了一个接口,后面的virtual函数继承,毫无意外的都是继承了一个接口。(当然,纯虚函数你也可以提供一份缺省实现,并且通过显式调用)
    • 接口继承加上抽象实现:当你使用非纯virtual继承的时候,你继承了一个接口,同时继承了一份抽象实现,我们也可以利用上面的接口继承加上缺省实现分开他们
    • 实现继承:声明non-virtual函数的目的就是为了让derived classes继承函数的接口以及一份强制性的实现。
  • 小心名称
    • 对于继承的时候,我们可能会因为重新命名导致derived class 的名称被遮掩:即重新在derived class命名一个和base class 名称相同的成员变量。此时会发生遮掩
    • 我们可以使用using表达式让被遮掩的变量重见天日

对于复合

  • 复合所塑造的关系为has a,即一个类为另外一个类的成员。对于class a,b,a包含b这个对象。a是一个大的对象
  • 注意区分复合与继承之间的差异

new与delete和他的小伙伴们

new与delete的使用

  • 成对使用new与delete,array new与array delete,placement new,placement delete
  • 注意对于使用别名的东西充满警惕,因为他有可能就是一个指针但是你没有看出来
  • 我们一般遵循“以对象的方式管理资源”使用shareptr,但是注意不能在管理资源的时候做其他动作。一定是要先管理资源。即先使用share-ptr,然后再进行接下来的操作
  • placement new placement delete,虽然placement delete只有在异常的时候调用,但是也需要写。
  • 选择适合的时机:改善性能时候,对heap收集调试信息,heap出错的时候

设置一个好的newhandler

  • newhandler使用时机:分配内存失败的时候
  • 好的newhandler需要做的事情
    • 让更多内存可被使用
    • 当当前这个newhandler无法处理的时候,使用另外一个
    • 抛出一个系统定义好的异常。

template

template前置知识

  • 谨记隐式接口:隐式接口指的是在模板中要求的对应的接口
  • 了解编译期多态:类似于函数重载,但是其再编译期就完成了选择
  • typename 的两个用法:作为<>的类名,以及告诉编译器这是一个嵌套类而不是作用域
  • 使用template时候应该注意,需要将参数无关的代码抽离
  • 和普通的类型转换一样,需要类型转换的时候,也需要为模板定义非成员函数

template的运行

  • 对于继承于其他类的模板。其基类往往都是被屏蔽的状态(因为可能的偏特化,编译器才这么做)如果需要这么用。可以考虑使用using声明来告诉他。这时可以用的
  • template可以用来编写任意类的成员函数。构造,拷贝等等都可以。我们可以用它来接收所有参数,也就是所谓的泛型编程