目录
- 类/对象
- 1.多态基类的析构函数应总是public virtual,否则应为protected
- 2.编译器会隐式生成默认构造,复制构造,复制赋值,析构,(C++11)移动构造,(C++11)移动赋值的inline函数
- 3.不要在析构函数抛出异常,也尽量避免在构造函数抛出异常
- 模板
- 函数
- 内存相关
- STL标准库
- 优化与效率
- 异常
- 杂项
- 参考
前言:C++是博大精深的语言,特性复杂得跟北京二环一样,继承乱得跟乱伦似的。
不过它仍然是我最熟悉且必须用在游戏开发上的语言,这篇文章用于挑选出一些个人觉得重要的条款/经验/技巧进行记录总结。
文章最后列出一些我看过的C++书籍/博客等,方便参考。
其实以前也写过相同的笔记博文,现在用markdown”重置“一下。
类/对象
1.多态基类的析构函数应总是public virtual,否则应为protected
当要释放多态基类指针指向的对象时,为了按正确顺序析构,必须得借助virtual从而先执行析构派生类再析构基类。
当基类没有多态性质时,可将基类析构函数声明protected,并且也无需耗费使用virtual。
2.编译器会隐式生成默认构造,复制构造,复制赋值,析构,(C++11)移动构造,(C++11)移动赋值的inline函数
当你在代码中用到以上函数时且没有声明该函数时,就会默认生成相应的函数。
特殊的,当你声明了构造函数(无论有无参数),都不会隐式生成默认构造函数。
不过隐式生成的函数比自己手写的函数(即使行为一样)效率要高,因为经过了编译器特殊优化。
(c++11)当你需要显式禁用生成以上某个函数时,可在函数声明尾部加上 = delete ,例如:
Type(const Type& t) = delete;
(c++11)当你需要显式默认生成以上某个函数时,可在函数声明尾部加上 = default ,例如:
Type(Tpye && t) = default;
3.不要在析构函数抛出异常,也尽量避免在构造函数抛出异常
析构函数若抛出异常,可能会使析构函数过早结束,从而可能导致一些资源未能正确释放。
构造函数若抛出异常,则无法调用析构函数,这可能导致异常发生前部分资源成功分配,却没能执行析构函数的正确释放行为。
模板
1. 不要偏特化模板函数,而是选择重载函数。
编译器匹配函数时优先选择非模板函数(重载函数),再选择模板函数,最后再选择偏特化模板函数。
当匹配到某个模板函数时,就不会再匹配选择其他模板函数,即使另一个模板函数旗下有更适合的偏特化函数。
所以这很可能导致编译器没有选择你想要的偏特化模板函数。
2.(C++11)不要重载转发引用的函数,否则使用其它替代方案
转发引用的函数是C++中最贪婪的函数,容易让需要隐式转换的实参匹配到不希望的转发引用函数。(例如下面)
template<class T>
void f(T&& value);
void f(int a);
//当使用f(long类型的参数)或者f(short类型的参数),则不会匹配int版本而是匹配到转发引用的版本
替代方案:
- 舍弃重载。换个函数名或者改成传递const T&形参。
- 使用更复杂的标签分派或模板限制(不推荐)。
函数
1.(C++11)禁用某个函数时,使用 = delete而非private
原因有4个:
- private函数仍需要写定义(即使那是空的实现),
- 派生类潜在覆盖禁用函数名的可能性,
- “=delete”语法比private语法更直观体现函数被禁用的特点,
- 在编写非类函数的时候,无法提供private属性。
一般 = delete的类函数应为public,因为编译器先检测可访问性再检验禁用性
2.(C++11)lambda表达式一般是函数对象。特殊地,在无捕获时是函数指针。
编译器编译lambda表达式时实际上都会对每个表达式生成一种函数对象类型,然后构造出函数对象出来。
特殊地,lambda表达式在无任何捕获时,会被编译成函数,其表达式值为该函数指针(毕竟函数比函数对象更效率)。
因此在一些老旧的C++API只接受函数指针而不接受std::function的时候,可以使用无捕获的lamdba表达式。
3.(C++11)尽可能使用lamada表达式代替std::bind
直接举例说明,假设有如下Func函数:
void Func(int a, float b);
现在我们让Func绑定上2.0f作为参数b,转化一个void(int a)的函数对象。
std::function<void(int)> f;
float b = 2.0f;
//std::bind写法
f = std::bind(Func, std::placeholders::_1, b);
f(100);
//lambda表达式写法
f = [b](int a) {Func(a, b); };
f(100);
可以看到使用std::bind会十分不美观不直观,还得注意占位符位置顺序。
而使用lambda表达式可以让代码变得十分简洁优雅。
4.(C++11)使用lambda表达式时,避免默认捕获模式
按引用默认捕获容易造成引用空悬,而显示的引用捕获更能容易提醒我们捕获的是哪个变量的引用,从而更容易理清该引用的生命周期。
按值默认捕获容易让人