effective cpp
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可以用来编写任意类的成员函数。构造,拷贝等等都可以。我们可以用它来接收所有参数,也就是所谓的泛型编程
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 PC!