本系列是开源书c++ best practises[1]的中文版,全书从工具、代码风格、安全性、可维护性、可移植性、多线程、性能、正确性等角度全面介绍了现代c++项目的最佳实践。本文是该系列的第三篇。
c++最佳实践:
1. 工具
2. 代码风格
3. 安全性(本文)
4. 可维护性
5. 可移植性及多线程
6. 性能
7. 正确性和脚本
安全性
尽量使用const
用const修饰变量或方法,从而告诉编译器这些都是不可变的,有助于编译器优化代码,并帮助开发人员了解函数是否有副作用。此外,使用const &可以防止编译器复制不必要的数据。john carmack对```const```的评论[2]值得一读。
// bad ideaclass myclass{public: void do_something(int i); void do_something(std::string str);};// good ideaclass myclass{public: void do_something(const int i); void do_something(const std::string &str);};
仔细考虑返回类型
getters(成员变量读取api)
正常情况下,通过返回值读取成员变量时,使用&或const &返回值可以显著提高性能
按值返回更有利于线程安全,如果返回的值就是为了复制使用,就不会有性能损耗
如果api返回值使用协变类型(covariant return types),必须返回&或*
临时值和局部值
始终按值返回
不要用const引用传递和返回简单类型
// very bad ideaclass myclass{public: explicit myclass(const int& t_int_value) : m_int_value(t_int_value) { } const int& get_int_value() const { return m_int_value; }private: int m_int_value;}
相反,通过值传递和返回简单类型。如果不打算更改传递的值,请将它们声明为const,但不要声明为const引用:
// good ideaclass myclass{public: explicit myclass(const int t_int_value) : m_int_value(t_int_value) { } int get_int_value() const { return m_int_value; }private: int m_int_value;}
为什么要这样?因为通过引用传递和返回会导致指针操作,而值传递在处理器寄存器中处理,速度更快。
避免访问裸内存
c++中很难在没有内存错误和泄漏风险[3]的情况下正确处理裸内存的访问、分配和回收,c++11提供了避免这些问题的工具。
// bad ideamyclass *myobj = new myclass;// ...delete myobj;// good ideaauto myobj = std::make_unique(constructor_param1, constructor_param2); // c++14auto myobj = std::unique_ptr(new myclass(constructor_param1, constructor_param2)); // c++11auto mybuffer = std::make_unique(length); // c++14auto mybuffer = std::unique_ptr(new char[length]); // c++11// or for reference counted objectsauto myobj = std::make_shared(); // ...// myobj is automatically freed for you whenever it is no longer used.
用std::array或std::vector代替c风格的数组
这两种方法都保证了对象的连续内存布局,并且可以(而且应该)完全取代c风格数组,另外这也是不使用裸指针的诸多原因之一。
另外,避免使用```std::shared_ptr```保存数组[4]。
使用异常
返回值(例如boost::optional),可以被忽略,如果不检查,可能会导致崩溃或内存错误,而异常不能被忽略。另一方面,异常可以被捕获和处理。可能异常会一直上升到应用程序的最高层级被捕获、记录到日志中,并触发应用自动重启。
c++的设计者之一stroustrup谈论过这个话题: why use exceptions?[5]
用c++风格的类型转换,而不是c风格的类型转换
用c++风格的强制类型转换(static_cast,dynamic_cast,…)代替c风格的强制类型转换,c++风格的强制转换允许更多的编译器检查,而且相当安全。
// bad ideadouble x = getx();int i = (int) x;// not a bad ideaint i = static_cast(x);
此外,c++类型转换风格更为显式,对搜索更为友好。
但如果需要将double类型转换为int类型,请考虑重构程序逻辑(例如,对溢出和下溢进行额外检查)。避免出现测量了3次,然后切割0.9999999999981次这种情况。
不要定义可变参数函数(variadic function)
可变参数函数可以接受数量可变的参数,最著名的例子可能是printf()。虽然可以定义此类函数,但可能存在安全风险。可变参数函数的使用不是类型安全的,错误的输入参数可能导致程序以未定义的行为终止。这种未定义的行为可能会导致安全问题。如果使用支持c++1的编译器,那么可以使用可变参数模板。
额外资源
david wheeler的《how to prevent the next heartbleed[7]》一书很好的分析了代码安全的现状以及如何确保代码安全。
玩游戏手机推荐:华为荣耀9,小米MIX,三星Galaxy C9 Pro,一加手机5齐上榜!
超过额定电压,使用独石陶瓷电容器会怎么样?
三星S8什么时候上市?3月底发布 4月10日接受预订!
新能源汽车迅速增长 车企“新四化”浪潮来势汹汹
导热塑料为电子元器件提供良好的散热及绝缘功能
现代C++项目的最佳实践
家门口的监控利器 体验斑点猫智能猫眼S200
河道水位监测站的特点及原理
共聚焦显微镜的结构
OLED存在技术缺陷,iPhoneX不是壮大OLED电视的灵丹妙药
抗扰性准测量标准
华为申请注册“华为全车智能”商标
恩智浦半导体将采用Fraunhofer MPEG音频编解码器解决方案
闻泰科技推出半导体设计制造一体化(IDM)模式
浅析内存基础知识:易失性、非易失性和持久性
魅族PRO7非常的尴尬:好不容易抱上高通的大腿,但是用不上
质量流量计的分类方式
联想拯救者游戏本刃7000P 2021详细参数
对于嵌入式为什么要有uboot的深度解析
常用的机器人传感器有哪些