C++是博大精深的语言,特性复杂得跟北京二环一样,继承乱得跟乱伦似的。

不过它仍然是我最熟悉且必须用在游戏开发上的语言,这篇文章用于挑选出一些个人觉得重要的条款/经验/技巧进行记录总结。

文章最后列出一些我看过的C++书籍/博客等,方便其他人参考。


类/对象:

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版本而是匹配到转发引用的版本

替代方案:

1,舍弃重载。换个函数名或者改成传递const T&形参。

2,使用更复杂的标签分派或模板限制(不推荐)。

函数:

1.(C++11)禁用某个函数时,使用 = delete而非private

其一:private函数仍需要写定义(即使那是空的实现),

其二:派生类潜在覆盖禁用函数名的可能性,

其三:“=delete”语法比private语法更直观体现函数被禁用的特点,

其四:在编写非类函数的时候,无法提供private属性。

(PS:一般 = 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();

//lambda表达式写法
f = [b](int a) {Func(a, b); };
f();

可以看到使用std::bind会十分不美观不直观,还得注意占位符位置顺序。

而使用lambda表达式可以让代码变得十分简洁优雅。

4.(C++11)使用lambda表达式时,避免默认捕获模式

按引用默认捕获容易造成引用空悬,而显示的引用捕获更能容易提醒我们捕获的是哪个变量的引用,从而更容易理清该引用的生命周期。

按值默认捕获容易让人误解lambda式是自洽的(即不依赖外部)。下面是一个典型例子:

void test() {
   static int a = 0;
   auto func = [=]() {
   return a + 2;
   };
   a++;
   int result = func();
}

由于默认捕获,你以为a是以按值拷贝过去,所以期待result总会会是2。但是实际上你是调用了同一个作用域的静态变量,没有拷贝的行为。

所以,无论是按值还是引用,都尽量指定变量,而不是用默认捕获。

内存相关:

1.检查new是否失败通常是无意义的。

new几乎总是成功的,现代大部分操作系统采取进程的惰式内存分配(即请求内存时不会立即分配内存,当使用时才慢慢吞吞分配)。

所以当使用new时,通常不会立即分配内存,从而无法真正检测到是否内存将会耗尽。

2.尽量避免多次new同一种轻量级类型,而是先new一个大区域再分配多次。

每次new的时候,实际上还会额外分配出一个存放内存信息的区域,而多次分配内存给轻量级类型时,会造成臃肿的内存信息。

而且在删除这些区域时,很容易造成很多块内存碎片,导致内存利用率不高。

所以应当使用内存池的方式,先new一大块区域,再从区域分配内存给轻量级类型。

STL标准库:

1.(C++11)使用emplace/emplace_back/emplace_front而不是insert/push_back/push_front

emplace 最大的作用是避免产生不必要的临时变量,因为它可以直接在容器相应的位置根据参数来构造变量。

而 insert / push_back / push_front 操作是会先通过参数构造一个临时变量,然后将临时变量移动到容器相应的位置。

2.在遍历容器时删除迭代器需谨慎

顺序式容器删除迭代器会破坏本身和后面的迭代器,节点式容器删除迭代器会破坏本身,导致循环遍历崩溃(循环遍历依赖于容器原有的迭代器)。

两个值得借鉴的正确做法:

auto it = vec.begin();
while (it != vec.end()){
    if (...){
        // 顺序式容器的erase()会返回紧随被删除元素的下一个元素的有效迭代器
        it = vec.erase(it);
    }
    else{
        it++;
    }
}
auto it = list.begin();
while (it != list.end()){
    if (...) {
        t.erase(it++);
    }
    else {
        it++;
    }
}

3.容器的at()会检查边界,[]则不检查边界

STL小细节。另外std::vector<bool>和std::bitset的[]提供的是值拷贝,而不是引用。

4.sort()的<比较操作符,若两者相等则必须返还失败

STL的sort算法基本是快排,是不稳定的排序。

若比较的两者相等时返还成功,则不稳定排序容易出现死循环,从而导致程序崩溃。

优化与效率:

1.在后期遇到性能瓶颈,万不得已时才使用inline

现代编译器已经十分智能,很多时候该写成inline的函数编译器会自动帮你inline,不该inline的时候即使你显式写了inline编译器也有可能认为不该inline。

也就是说显式的写出inline只是给编译器一个建议,它不一定会采纳。

因此在开发时不用过早优化,过早考虑inline,而是遇到性能瓶颈时才考虑使用显式写出inline,不过大部分这时候你更应该考虑的是你写的算法效率。

异常:

1.(C++11)若保证异常不会抛出,应使用noexpect异常规格,否则不要声明异常规格。

无声明异常规格,意思是可能抛出任何异常。

相比无声明异常规格的函数,noexpect函数能得到编译器的优化(发生异常时不必解开栈),且能清晰表示自己的无异常保证。

杂项:

1.(C++17)需要用到任意可变的类型时,使用std::any,std::variant而不是union

union是从c继承来的特性,它的成员不可以是带构造函数,析构函数,自定义复制构造函数的c++类。

因此最好不要使用union,而是用std::any或std::variant ,目前C++17已引入<any>库和<variant>库。

2.(C++11)auto只能推导出类型型别,而decltype能够推导出声明型别

int& value = 233;
auto a = value;//auto是int类型
decltype(auto) b = value; //decltype(auto)是int&类型

也就是说auto的推导类型会抛弃引用性质,而decltype能够推导出完整的声明类型。

3.(C++11)使用nullptr而不是NULL或0

NULL是C语言遗留的东西,是将宏定义成0的,容易造成指针和整数的二义性。

而nullptr很好的避免了整数的性质。

4.(C++11)使用enum class语法为枚举类型提供限定范围

C带来的enum语法是允许枚举类进行隐式转换的,潜在造成程序员不希望发生的转换。

而C++11的enum class会阻止隐式转换,需要程序员显示转换

enum class Color{Red,Blue,Green};
Color color = Color::Red;
int i = static<int>(color);

5.(C++11)只要潜在编译期可计算的函数/变量,就使用constexpr

constexpr能让一些函数/变量在编译期就可计算,可减少运行期运算。(可视作模板元运算的美化语法)

此外,constexpr如果接受的是运行期变量/参数,则会变成运行期计算。

也就是说它既可用作编译期运算,也可运行期运算,语境作用域比非constexpr更广。


参考资料:

《C++ Primer Plus》:当初入门C++语言的书籍。

《C++程序设计语言(特别版)》:C++之父编写的入门教材,但实际上更应该算为介于入门与进阶之间的工具书(用于查询语法)。

《Effective C++》:C++ 进阶书,深入理解与经验

《More Effective C++》:C++ 进阶书,深入理解与经验

《深度探索C++对象模型》:C++ 进阶书,深入理解

《Expectional C++》:C++ 进阶书,深入理解与经验

《高速上手 C++11/14/17》:C++11/14/17 入门书,介绍C++11/14/17各项新特性的基础用法,它目前只有电子版本: https://github.com/changkun/modern-cpp-tutorial/blob/master/book/zh-cn/toc.md

《Effective Modern C++》:C++11/14 进阶书,介绍C++11/14部分新特性的深入理解与经验。

实际上博主学的C++知识/经验/理解很多也是从实际项目获得的,也就不能作为书名列举了。

C++是非常非常复杂的语言,看的这方面书越多就越觉得自己的无知(例如C++Boost绝对会让你大开眼界的)。

但是在学习C++的中途也必须认识到,C++是一门工具,不要过多钻C++语言的牛角尖。

谨记:程序员是要成为工程师而不是语言学家。

C++ 编程技巧笔记记录(持续更新)的更多相关文章

  1. Linux系统编程重要细节记录(持续更新中)

    1.在打印rlim_t值时,需要将其转换为long long并使用%lld printf()修饰符.

  2. C#技巧记录——持续更新

    作为一名非主修C#的程序员,在此记录下学习与工作中C#的有用内容,持续更新 对类型进行约束,class指定了类型必须是引用类型,new()指定了类型必须具有一个无参的构造函数 where T : cl ...

  3. 状压dp(状态压缩&&dp结合)学习笔记(持续更新)

    嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客) 但是,依旧懵逼·································· 这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的 ...

  4. ASP.NET MVC 5 系列 学习笔记 目录 (持续更新...)

    前言: 记得当初培训的时候,学习的还是ASP.NET,现在回想一下,图片水印.统计人数.过滤器....HttpHandler是多么的经典! 不过后来接触到了MVC,便立马爱上了它.Model-View ...

  5. Objective-C Reflection(Objective-C 反射机制)实用随笔笔记(持续更新)

    前言:本篇文章就"Objective-C 反射机制"使用方面进行叙述,不会涉及太多理论论述,因为"Objective-C 反射机制"理论论述在网上搜索一大把,本 ...

  6. ReactNative开发笔记(持续更新...)

    本文均为RN开发过程中遇到的问题.坑点的分析及解决方案,各问题点之间无关联,希望能帮助读者少走弯路,持续更新中... (2019年3月29日更新) 原文链接:http://www.kovli.com/ ...

  7. Oracle 9i & 10g编程艺术-深入数据库体系结构-学习笔记(持续更新中)

    --20170322 --1.0 --更新表的统计信息begin dbms_stats.set_table_stats(user,'EMP',numrows => 10000);end; beg ...

  8. 我的 CSDN 博客目录索引(主要记录了我学习视频、书籍的笔记,持续更新中)

    我的 CSDN 博客地址: lw_power的专栏 - 博客频道 - CSDN.NEThttp://blog.csdn.net/lw_power 佟刚老师<Spring4视频教程>学习笔记 ...

  9. 《CLR Via C#》读书笔记,持续更新...

    写了快5年代码了,一直都是使用别人发明的语言,别人发明的框架做快速开发,还从来没有真正深刻的学习过底层的一些东西,于是今年我打算读<CLR Via C#>这本书,认识一下C#的底层,我觉得 ...

随机推荐

  1. bzoj 1064 假面舞会 图论??+dfs

    有两种情况需要考虑 1.链:可以发现对最终的k没有影响 2.环:如果是真环(即1->2->3->4->1),可以看出所有可行解一定是该环的因数 假环呢??(1->2-&g ...

  2. 虚拟机console基础环境部署——配置本地YUM源

    1. CD/ROM装载系统镜像2. 挂载设备3. 配置本地源4. 总结 有关YUM源及Linux系统三大软件管理方式,参照博客<CentOS系统三大软件管理>,笔记内链:CentOS系统三 ...

  3. Android P Beta发布!最新版本抢先体验!

    在不久前结束的谷歌I/O开发者大会上,谷歌公布了下一个版本的 Android,也就是 Android P 的 beta 版本.Android P 将 AI 定位为操作系统的核心,并侧重于提供智能且简洁 ...

  4. nginx+php安装

    1.环境概述 虚拟机系统:CentOS Linux release 7.3.1611 (Core) 宿主机系统:Mac Sierra version 10.12.3 nginx:1.10.3 php: ...

  5. CentOS DesktopEntry

    IBM Developer  :  https://www.ibm.com/developerworks/cn/linux/l-cn-dtef/index.html [Desktop Entry] N ...

  6. android双待手机获取每一张SIM卡的imei

    /** * create a TelephonyInfo.java class */import java.lang.reflect.Method; import android.content.Co ...

  7. MySQL之父造访腾讯云 为腾讯云数据库开源点赞

    近日,技术大牛 MariaDB 公司创始人兼CTO Michael Widenius(又名Monty).MariaDB 基金会主席 Kaj 来到中国,针对MariaDB与腾讯云的技术合作进行回访.去年 ...

  8. 使用Keepalived构建LVS高可用集群

    LVS的DR模型配置+Keepalive部署 介绍 下图为DR模型的通信过程,图中的IP不要被扑结构中的IP迷惑,图里只是为了说明DR的通信原理,应用到本例中的拓扑上其工作原理不变. 拓扑结构 服务器 ...

  9. HighChar 案例

    Highchars //前台 <script> $(function () { //showChat(); initChat(); showPie(); initPie(); }) fun ...

  10. 第12章 X.509证书库的Fluent API - IdentityModel 中文文档(v1.0.0)

    存储X.509证书的常见位置是Windows X.509证书存储区.商店的原始API有点神秘(在.NET Framework和.NET Core之间也略有变化). X509类是一个简化的API从所述存 ...