本文章属于专栏《业界Cpp进阶建议整理》
本文列出《More Effective C++》的1-10条的个人理解的极精简版本。
- 1、仔细区分pointers和references
- 使用引用的情况:
- 一旦代表的该对象就不能改变,应该选择reference(优势是使用时不需要判是否空)。
- 实现一个操作符时,为了方便读写,返回引用
- 其他任何时候,用pointers
- 使用引用的情况:
- 2、最好使用C++转型操作符
- 个人见解:在性能要求不高的地方尽量使用C++转型操作符,以降低阅读、维护成本。不在极高频调用的代码中使用,以降低机器成本
- 3、绝对不要以多态方式处理数组(std::array)
- 核心是不要在std::array中存派生类对象,然后传给处理array[基类]的函数。因为传入后,在以下两个场景使用是有问题的
- array遍历元素,是按照指针类型做间隔(派生类比基类大,会导致偏移错误)
- 通过基类指针删除一个派生类构成的数组,在C++中是未定义的。
- 常用的正确的做法是用vector,存储基类指针(派生类对象永远不要直接赋值给基类对象,会导致部分覆盖)
- 个人见解:在业务代码中放弃使用std::array,固定长度的数组意味着一旦改变就意味着重启。且在大部分场景性能收益不大
- 4、非必要不提供默认构造函数(不要参数的,系统默认创建的构造函数)
- 《more effective cpp》作者认为两种情况下是必要的
- A a[10],放在数组中,没有办法进行传参初始化(不过这个场景并不常用,即使用,也是低频场景,这个时候用,使用vector + for循环初始化,也是够用的)
- 基类没有默认初始化函数时,派生类需要在构建时,需要显示初始化基类
- 个人理解:
- 同上一条类似,放弃a[10]这种原始数组,使用std::vector。
- 做为基类,有一个默认构造函数,可以节省一些代码,特别是继承层数比较多的情况
- 《more effective cpp》作者认为两种情况下是必要的
- 5、不要提供转换函数
- 单自变量的构造函数和隐式转换操作符(如operator double()),会提供隐式转换的功能
- 尽量不要提供隐式转换的功能,如在cout << a时,如果a本身没有写<<操作符,但是a能隐式转换类型,则会先转换类型,再调用该类型的 <<。这类操作很容易出现不符合预期的结果。
- 用explicit来禁止,单变量的构造函数的隐式转换
- 6、区别++、--的前置和后置的区别
- A& operator++() 返回自身。【前置++i】
- const A operator++(int) 返回一个当前对象的copy,然后对自身对象+1。【后置i++】
- 个人见解:在循环时,永远使用前置。在业务逻辑中,不管是前置还是后置,都单独写一行,性能一样,且代码更易读,放弃if(fun(a++))这种写法。改为if(run(a)) & a++
- 7、千万不要重载 && ,|| 和,
- 实际用的时候,期望 &&前面失败后面就不执行,但是重载的时候做不到
- 8、了解各种不同意义的new和delete
- new
- new operator:
- 说明:语言内构建,不能被改变意义,总是做相同的事:1、分配足够的内存 2、调用构造函数给分配的内存设定初值。可以改变实现方式,但是不能改变函数步骤和语义
- 例子: A* a = new A();
- operator new:
- 说明:这是一个函数,可以被重写或者重载,它除了内存分配,不会做任何事情,形式为:void* operator new(size_t size);
- 例子: void* rawMemory = operator new(sizeof(A));
- placement new
- 将已有对象,构建在指定内存地址上
- 例子:void *buffer = operator new(sizeof(int)); new (buffer) A a;
- 这里获取buffer时,调用一个 void* operator new(size_t)函数,得到一个内存空间
- 然后调用void operator new(size_t, void* local) {return loacl},并在该函数返回的指针上面调用构造函数
- new operator:
- delete
- 当使用placement new时,要先调用析构函数,a->~A(); 然后再调用自己写的函数,释放内存,毕竟只有自己知道内存空间是如何创建的
- 个人见解:放弃为了性能overwrite new/delete,使用tcmalloc或者jecmalloc。参考我的文章《c++的高性能内存管理库tcmalloc和jemalloc》
- new
- 9、利用destructors避免泄漏资源
- 本质就是把heap指针,放到栈上对象,保证释放时,内存也会被释放。如智能指针
- 10、防止constructor泄漏资源
- 本质相同,就是用stack对象管理heap对象,也就是对象的所有heap的成员对象,都用智能指针管理