C++最佳实践之编程建议
本文介绍C++的编程建议基于C++之父Bjarne Stroustrup编写的《A Tour of C++》,包括通用指南、命名空间、异常处理、成员函数、虚函数、构造函数、模板、容器、stl标准库、线程与并发控制。
目录
一、C++通用指南
二、智能指针与内存
三、命名空间与异常处理
四、成员函数与虚函数
五、构造函数与类型转换
1、构造函数
2、default默认操作
3、delete禁止操作
4、单参数的构造函数
5、禁止不同类型的拷贝
六、template模板
七、stl标准库
八、容器
九、线程与并发控制
1、线程初始化
2、mutex与lock
一、C++通用指南
通用的编程指南建议如下:
- 保持函数简洁,保持代码简洁;
- 关注编程技术,而不是语言特点;
- 函数应该执行单个逻辑操作, 不要把多个功能放在同一个函数实现;
- 当函数在不同类型执行相同任务时,使用函数重载;
- 如果函数在编译期能确定,那么使用constexpr关键字修饰;
- 避免复杂表达式;
- 避免变窄转换,防止精度丢失,比如float类型转为int类型;
- 变量的作用域最小化,能用局部变量就不要声明为全局变量;
- 避免魔术常量,使用符号常量;
- 优先考虑不可变的数据,比如参数不用修改,就声明为const;
- 避免相似的命名,防止产生歧义;
- 对于具有命名类型的声明,首选大括号{}来初始化;
- 使用auto关键字避免重复类型命名;
- 避免未初始化的变量;
- 仅在位运算时使用unsigned修饰符;
- 保持指针的使用简单直接;
- 使用nullptr,而不用0或者NULL;
- 能用代码表达,就不用过多注释;
- 保持一致的缩进样式;
- 避免使用goto跳转;
- 使用==代替strcmp来判断字符串是否相等;
- 使用new代替malloc来申请内存;
- 不要用longjmp(),不要用exit(),而是抛出异常;
- 使用<chrono>代替<ctime>,如果用到时间;
二、智能指针与内存
C++提供三种智能指针:shared_ptr、unique_ptr和weak_ptr,而auto_ptr已经过时不建议使用。智能指针用于管理内存分配与释放,其中shared_ptr采用引用计数法,当计数为0就会释放内存,对象共享一段内存;而unique_ptr独自拥有管理内存的所有权;weak_ptr作为弱指针,观察shared_ptr管理的内存对象,可以避免shared_ptr的循环引用导致的内存泄漏。
观察下面这段代码有什么问题:
void circle(int x)
{Shape∗ p = new Circle{Point{0,0},10};// ...if (x<0) throw Exception(); // potential leakif (x==0) return; // potential leak// ...delete p;
}
当x < 0时,抛出异常;当x == 0时,程序返回。这两种情况没有调用delete p来释放内存对象,导致内存泄漏。建议的解决方案使用智能指针,来避免忘记释放内存。
再看另一段代码:
Shape∗ circle(int x)
{Shape∗ p = new Circle{Point{0,0},10};// ...return p;
}
这里返回本地对象(局部变量)的引用,函数执行完毕,对象已经释放,返回的指针地址成为野指针 。所以,不能返回本地对象的引用。
智能指针的使用与避免内存泄漏的建议如下:
- 使用unique_ptr或shared_ptr避免忘记释放new创建的对象;
- 当需要拷贝锁或者更小粒度的同步控制,优先使用unique_ptr;
- 当与condition_variable结合使用时,优先使用unique_ptr;
- 尽量使用make_shared代替shared_ptr;
三、命名空间与异常处理
命名空间用于防止命名冲突、模块隔离。关于命名空间与异常处理建议如下:
- 使用头文件来表示接口和强调逻辑结构;
- 源文件使用#include头文件,要实现它声明的函数;
- 避免在头文件写非内联函数,头文件仅支持内联函数;
- 不要在头文件使用using命名空间,最小化使用命名空间;
- 函数执行出错,而且错误影响到调用函数,那么抛出异常;
- 如果不确定使用异常还是错误码,那么优先使用异常;
- 能够在编译期检查就在编译期检查,不要等到运行期;
- 小量数据使用值传递,大量数据使用引用传递;
- 优先采用常量引用,而不是普通引用;
四、成员函数与虚函数
关于成员函数与虚函数的使用建议如下:
- 对称运算符使用非成员函数,仅当需要被类直接访问时才设为成员函数;
- 把成员函数声明为对象状态不可修改;
- 如果构造函数需要资源,那么它的类需要释构函数来释放资源;
- 如果类是一个容器,赋予它一个初始化列表的构造函数;
- 当接口和实现完全分离时,使用抽象类作为接口;
- 通过指针或引用来访问多态对象;
- 抽象类不需要构造函数;
- 有虚函数的类,需要虚的释构函数;
- 设计类的层次结构时,区分实现继承和接口继承;
五、构造函数与类型转换
1、构造函数
构造函数包括默认构造、拷贝构造、移动构造、拷贝赋值构造、移动赋值构造。而释构函数只有一个,并且无参数无返回值。示例代码如下:
class X {
public:X(param); // normal constructor: create an objectX(); // default constructorX(const X&); // copy constr uctorX(X&&); // move constr uctorX& operator=(const X&); // copy assignment: clean up target and copyX& operator=(X&&); // move assignment: clean up target and move~X(); // destructor: clean up
};
对象可以被拷贝或移动有以下五种情况:
- 作为赋值的来源;
- 作为对象的初始化;
- 作为函数参数;
- 作为函数返回值;
- 作为一个异常;
2、default默认操作
如果期望某个构造函数作为默认构造,那么使用=default。示例代码如下:
class Y {
public:Y(Sometype);Y(const Y&) = default; // default copy constructorY(Y&&) = default; // default move constructor
};
3、delete禁止操作
为了防止调用者不恰当调用,可以在构造函数加上=delete表示禁止某个操作。如果调用=delete修饰的操作,会在编译期报错。示例代码如下:
class Shape {
public:Shape(const Shape&) =delete; // no copy operationShape& operator=(const Shape&) =delete; // no assign operation
};void copy(Shape& s1, const Shape& s2)
{s1 = s2; // error: Shape copy is deleted
}
4、单参数的构造函数
默认把单个参数的构造函数声明为explicit,防止被隐式调用。示例代码如下:
class Hello {
public:explicit Hello(int a);
}
5、禁止不同类型的拷贝
如果默认值不适用某个类型,禁止拷贝。
六、template模板
为了定义优秀的模版,我们需要遵循以下机制:
- 数值依赖类型:可变模版;
- 编译期选择机制:if constexpr;
- 查询类型和表达式属性的机制;
template模板有如下特点:
- 能够将类型作为参数传递,而不丢失信息;
- 在模版实例化时,把不同上下文信息组织在一起;
- 将常量值作为参数传递,这意味着能够在编译期执行计算;
关于模板的建议如下:
- 使用模板表达适用多种参数类型的算法;
- 使用模板表达容器;
- 使用模板来提升抽象代码的等级;
- 让构造函数或函数模板来推断类模板参数类型;
- 虚成员函数不能作为模板成员函数;
- 使用模板别名来简化表示并隐藏细节;
- 模板提供编译期的动态类型;
七、stl标准库
C++提供stl标准库,包括:algorithm、array、vector、map、string、deque、list、thread等。关于stl标准库使用建议如下:
- 尽量使用标准库,不要自己造轮子;
- 不要认为标准库能使用所有处理情况;
- 使用标准库时,前面加std来引用,比如字符串std::string;
- 使用substr()来读子字符串,replace()写子字符串;
- 当进行范围检查时使用at(),当需要优化速度时使用iterator或[];
- 使用c_str()来表示C语言风格的字符串;
八、容器
C++提供的标准容器包括:vector、list、forward_list、deque、set、multiset、map、
multimap、unordered_map、unordered_multimap、unordered_set、unordered_multiset。
关于容器的使用建议如下:
扩容操作使用resize()而不是realloc();
插入操作使用push_back()或者insert(),效率比较高;
map基于红黑树,有序但效率较低。unordered_map基于哈希表,无序但效率较高;
九、线程与并发控制
1、线程初始化
使用函数或结构体作为执行任务,线程初始化的示例代码如下:
void f(); // 函数
struct F { // 函数对象void operator();
};void hello()
{thread t1 {f};thread t2 {F()};t1.join(); // 等待线程执行完毕t2.join();
}
2、mutex与lock
多线程并发控制,需要用到mutex互斥锁与lock结合使用,或者condition_variable条件变量。其中,lock有scoped_lock、unique_lock、shared_lock、lock_guard。一般情况下,读数据用shared_lock,写数据用unique_lock()。示例代码如下:
shared_mutex mtx; // 互斥锁
void read_data()
{shared_lock lock {mtx};
// 读操作
}
void writer()
{unique_lock lock {mtx};
// 写操作
}
关于线程与并发控制的建议如下:
- 使用condition_variable条件变量进行多线程通信;
- 不要在条件变量情况下等待(容易死锁);
- 线程任务使用promise返回结果,从future获取结果;
- 使用async()启动简单任务;
至此,C++语言的编程建议介绍完毕。没有严格约束,但是遵循可以提高编程效率,提高代码可读性,有利于团队协同编程。如有错漏,欢迎伙伴们指出纠正。
C++最佳实践之编程建议相关推荐
- 软件项目最佳实践: 可编程的权限控制
续 软件项目最佳实践: 又谈权限管理 当我们面对复杂的权限控制一愁莫展时,因为未来不明确需求而烦恼时,我们期望项目的权限控制是可编程的,但手中的代码不堪入目,只能暗自发誓接手下一个新项目时,一定重新设 ...
- 愿逝者安息,生者坚强 !!!- 民间捐助的最佳实践及捐助建议
奇迹 - 绵阳北川平安希望小学全体师生600余人无一伤亡,全体平安!!! 为什么?建造学校的执行力出色. 这次地震的规模如此之大,受灾面之广,前所未有.每个中国人都在积极捐款,许多人做志愿者,许多人献 ...
- Laravel最佳实践--事件驱动编程
在这篇文章中我们将了解到什么是"事件驱动编程"以及在Laravel中如何开始构建一个事件驱动应用,同时我们还将看到如何通过事件驱动编程来对应用程序的逻辑进行解耦. 在开始之前,先说 ...
- 针对《等保2.0》要求的云上最佳实践——网络安全篇
简介:伴随着国内企业上云步伐的加快,越来越多的企业需要对云上关键业务进行等级保护自查或完成相关认证.本文以<GB/T 22239-2019 信息安全技术 网络安全等级保护基本要求>中所要求 ...
- 在SQL Server中配置索引创建内存设置的最佳实践
介绍 (Introduction) The Index Create Memory setting is an advanced SQL Server configuration setting wh ...
- 高性能网站建设进阶指南:Web开发者性能优化最佳实践 pdf扫描版
高性能网站建设进阶指南:Web开发者性能优化最佳实践是<高性能网站建设指南>姊妹篇.作者Steve Souders是Google Web性能布道者和Yahoo!前首席性能工程师.在本书中, ...
- SVN分支/合并原理及最佳实践
#SVN分支/合并原理及最佳实践 SVN是一种常用的版本控制工具,一种典型的项目代码实践方式是: 存在一个代码基线(Base Line)或称主干,不同的模块使用各自的分支进行功能开发,在开发完毕后合并 ...
- ci/cd自动化测试_CI / CD管道加快测试自动化的16种最佳实践
前言: 知其然,知其所以然.相较于DevOps而言,CI/CD是一个相对具象的概念.在 IT 企业中,CI/CD的应用愈加广泛,成为推动软件研发活动的重要基础设施服务,同时推动 DevOps 模式的实 ...
- 【360开源】Pika最佳实践
奇技指南: Pika是360 热门的c++开源项目,基于rocksdb开发的大容量类Redis存储,力求在完全兼容Redis协议.继承Redis便捷运维设计的前提下通过持久化存储方式解决Redis在大 ...
最新文章
- C# 虚函数和重载函数
- 【设计模式】开闭原则
- scratch少儿编程第一季——07、人要衣装佛靠金装——外观模块
- 论文学习21-Globally Normalized Transition-Based Neural Networks(2016,标签偏差问题
- 得推B2B2C商城源码v4.1
- ICCV 2019丨微软亚研院精选论文解读
- java 计算器api_用JAVA编写一个简单的计算器~要使用接口的~急啊~
- Ubuntu下Linux系统文件恢复
- 那些在错误道路上一路狂奔的国产VR
- [iphone-游戏]游戏中常用的数据组织方式和解析
- mapper同时添加数据只能添加一条_神器之通用mapper的使用
- 【AWVS12】安全漏洞扫描工具,使用详解
- 获取随机数的n种方法,你知道几种
- PPT计算机原理结构初步,测量实践初步(赖丽娟).ppt
- 【情人节警报】看我如何智斗陌陌情爱骗子
- 软工-点赞和取消点赞
- JAVA通过tcp通信劳易测BCL 308i扫码枪获取数据
- 小明发布_LPL官方纪录片《来者何人》发布,比起FPX,RNG问题要严重多得多
- com.sec.android.app.keyguard,android - 查找/ system / app的APK名称 - 堆栈内存溢出
- 内网渗透之CFS三层靶机搭建
热门文章
- 如何让企业培训有效落地
- 嵌入式Linux能提供最小延时,嵌入式Linux的实时性分析和改进
- 2023年高新技术企业申报要点,建议收藏
- 大数据分析对企业有什么好处
- Django-知识回顾做个小DEMO
- win10 linux 1903,win10最新版本1903新功能
- 【光波电子学】MATLAB绘制光纤中线性偏振模式LP之单模光纤的电场分布(光斑)
- 借助nat123软件快速发布网站做网站服务,解决80端口被屏蔽被封,nat123软件原理分析
- 第十五届全国大学生智能汽车竞赛-双车组三轮图像处理总结(已开源)
- MediaPlayer实现金额的语音播报功能 1