[C++]高效C/C ++编程tips

 

Effective C++

  1. 视C++ 为一个语言联邦(C、Object-Oriented C++、Template C++、STL)
  2. 宁可以编译器替换预处理器(尽量以constenuminline替换#define
  3. 尽可能使用const
  4. 确定对象被使用前已先被初始化(构造时赋值(copy 构造函数)比default 构造后赋值(copy assignment)效率高)
  5. 了解C++ 默默编写并调用哪些函数(编译器暗自为class 创建default 构造函数、copy 构造函数、copy assignment 操作符、析构函数)
  6. 若不想使用编译器自动生成的函数,就应该明确拒绝(将不想使用的成员函数声明为private,并且不予实现)
  7. 为多态基类声明virtual 析构函数(如果class 带有任何virtual 函数,它就应该拥有一个virtual 析构函数)
  8. 别让异常逃离析构函数(析构函数应该吞下不传播异常,或者结束程序,而不是吐出异常;如果要处理异常应该在非析构的普通函数处理)
  9. 绝不在构造和析构过程中调用virtual 函数(因为这类调用从不下降至derived class)
  10. operator=返回一个reference to *this(用于连锁赋值)
  11. operator=中处理“自我赋值”
  12. 赋值对象时应确保复制“对象内的所有成员变量” 及“所有base class 成分”(调用基类复制构造函数)
  13. 以对象管理资源(资源在构造函数获得,在析构函数释放,建议使用智能指针,资源取得时机便是初始化时机(Resource Acquisition Is Initialization,RAII))
  14. 在资源管理类中小心copying 行为(普遍的RAII class copying 行为是:抑制copying、引用计数、深度拷贝、转移底部资源拥有权(类似auto_ptr))
  15. 在资源管理类中提供对原始资源(raw resources)的访问(对原始资源的访问可能经过显式转换或隐式转换,一般而言显示转换比较安全,隐式转换对客户比较方便)
  16. 成对使用new和delete时要采取相同形式(new中使用[]delete []new中不使用[]delete
  17. 以独立语句将newed 对象存储于(置入)智能指针(如果不这样做,可能会因为编译器优化,导致难以察觉的资源泄漏)
  18. 让接口容易被正确使用,不易被误用(促进正常使用的办法:接口的一致性、内置类型的行为兼容;阻止误用的办法:建立新类型,限制类型上的操作,约束对象值、消除客户的资源管理责任)
  19. 设计class 犹如设计type,需要考虑对象创建、销毁、初始化、赋值、值传递、合法值、继承关系、转换、一般化等等。
  20. 宁以pass-by-reference-to-const 替换pass-by-value (前者通常更高效、避免切割问题(slicing problem),但不适用于内置类型、STL迭代器、函数对象)
  21. 必须返回对象时,别妄想返回其reference(绝不返回pointer 或reference 指向一个local stack 对象,或返回reference 指向一个heap-allocated 对象,或返回pointer 或reference 指向一个local static 对象而有可能同时需要多个这样的对象。)
  22. 将成员变量声明为private(为了封装、一致性、对其读写精确控制等)
  23. 宁以non-member、non-friend 替换member 函数(可增加封装性、包裹弹性(packaging flexibility)、机能扩充性)
  24. 若所有参数(包括被this指针所指的那个隐喻参数)皆须要类型转换,请为此采用non-member 函数
  25. 考虑写一个不抛异常的swap 函数
  26. 尽可能延后变量定义式的出现时间(可增加程序清晰度并改善程序效率)
  27. 尽量少做转型动作(旧式:(T)expressionT(expression);新式:const_cast<T>(expression)dynamic_cast<T>(expression)reinterpret_cast<T>(expression)static_cast<T>(expression)、;尽量避免转型、注重效率避免dynamic_casts、尽量设计成无需转型、可把转型封装成函数、宁可用新式转型)
  28. 避免使用handles(包括引用、指针、迭代器)指向对象内部(以增加封装性、使const 成员函数的行为更像const、降低“虚吊号码牌”(dangling handles,如悬空指针等)的可能性)
  29. 为“异常安全” 而努力是值得的(异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏,分为三种可能的保证:基本型、强列型、不抛异常型)
  30. 透彻了解inlining 的里里外外(inlining 在大多数C++ 程序中是编译期的行为;inline 函数是否真正inline,取决于编译器;大部分编译器拒绝太过复杂(如带有循环或递归)的函数inlining,而所有对virtual 函数的调用(除非是最平淡无奇的)也都会使inlining 落空;inline 造成的代码膨胀可能带来效率损失;inline 函数无法随着程序库的升级而升级)
  31. 将文件间的编译依存关系降至最低(如果使用object references 或object pointers 可以完成任务,就不要使用objects;如果能够,尽量以class 声明式替换class 定义式;为声明式和定义式提供不同的头文件)
  32. 确定你的public 继承塑模出is-a(是一种)关系(适用于base classes 身上的每一件事情一定适用于derived classes 身上,因为每一个derived class 对象也都是一个base class 对象)
  33. 避免遮掩继承而来的名字(可使用using 声明式或转交函数(forwarding functions)来让被遮掩的名字再见天日)
  34. 区分接口继承和实现继承(在public 继承之下,derived classes 总是继承base class 的接口;pure virtual 函数只具体指定接口继承;非纯impure virtual 函数具体指定接口继承及缺省实现继承;non-virtual函数具体指定接口继承以及强制性实现继承)
  35. 考虑virtual函数以外的其他选择(如Template Method设计模式的non-virtual interface(NVI)手法,将virtual函数替换为“函数指针成员变量”,以tr1::function成员变量替换virtual函数,将继承体系内的virtual函数替换为另一个继承体系内的virtual函数)
  36. 绝不重新定义继承而来的non-virtual 函数
  37. 绝不重新定义继承而来的缺省参数值,因为缺省参数值是静态绑定(statically bound),而virtual 函数却是动态绑定(dynamically bound)
  38. 通过复合塑模has-a(有一个)或“根据某物实现出”(在应用域(application domain),复合意味has-a(有一个);在实现域(implementation domain),复合意味着is -implemented-in-terms-of(根据某物实现出))
  39. 明智而审慎地使用private 继承(private 继承意味着is-implemented-in-terms-of(根据某物实现出),尽可能使用复合,当derived class 需要访问protected base class 的成员,或需要重新定义继承而来的时候virtual 函数,或需要empty base 最优化时,才使用private 继承)
  40. 明智而审慎地使用多重继承(多继承比单一继承复杂,可能导致新的歧义性,以及对virtual 继承的需要,但确有正当用途,如“public 继承某个interface class” 和“private 继承某个协助实现的class”;virtual 继承可解决多继承下菱形继承的二义性问题,但会增加大小、速度、初始化及赋值的复杂度等等成本)
  41. 了解隐式接口和编译期多态(class 和templates 都支持接口(interfaces)和多态(polymorphism);class 的接口是以签名为中心的显式的(explicit),多态则是通过virtual 函数发生于运行期;template 的接口是奠基于有效表达式的隐式的(implicit),多态则是通过template 具现化和函数重载解析(function overloading resolution)发生于编译期)
  42. 了解typename 的双重意义(声明template 类型参数是,前缀关键字class 和typename 的意义完全相同;请使用关键字typename 标识嵌套从属类型名称,但不得在基类列(base class lists)或成员初值列(member initialization list)内以它作为base class 修饰符)
  43. 学习处理模板化基类内的名称(可在derived class templates内通过this->指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成)
  44. 将与参数无关的代码抽离templates(因类型模板参数(non-type template parameters)而造成代码膨胀往往可以通过函数参数或class 成员变量替换template 参数来消除;因类型参数(type parameters)而造成的代码膨胀往往可以通过让带有完全相同二进制表述(binary representations)的实现类型(instantiation types)共享实现码)
  45. 运用成员函数模板接受所有兼容类型(请使用成员函数模板(member function templates)生成“可接受所有兼容类型” 的函数;声明member templates 用于“泛化copy 构造” 或“泛化assignment 操作” 时还需要声明正常的copy 构造函数和copy assignment 操作符)
  46. 需要类型转换时请为模板定义非成员函数(当我们编写一个class template,而它所提供之“与此template 相关的” 函数支持“所有参数之隐式类型转换” 时,请将那些函数定义为“class template 内部的friend 函数”)
  47. 请使用traits classes 表现类型信息(traits classes 通过templates 和“templates 特化” 使得“类型相关信息” 在编译期可用,通过重载技术(overloading)实现在编译期对类型执行if...else 测试)
  48. 认识template 元编程(模板元编程(TMP,template metaprogramming)可将工作由运行期移往编译期,因此得以实现早期错误侦测和更高的执行效率;TMP 可被用来生成“给予政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码)
  49. 了解new-handler 的行为(set_new_handler 允许客户指定一个在内存分配无法获得满足时被调用的函数;nothrow new 是一个颇具局限的工具,因为它只适用于内存分配(operator new),后继的构造函数调用还是可能抛出异常)
  50. 了解new 和delete 的合理替换时机(为了检测运用错误、收集动态分配内存之使用统计信息、增加分配和归还速度、降低缺省内存管理器带来的空间额外开销、弥补缺省分配器中的非最佳齐位、将相关对象成簇集中、获得非传统的行为)
  51. 编写new 和delete 时需固守常规(operator new 应该内涵一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就应该调用new-handler,它也应该有能力处理0 bytes 申请,class 专属版本则还应该处理“比正确大小更大的(错误)申请”;operator delete 应该在收到null 指针时不做任何事,class 专属版本则还应该处理“比正确大小更大的(错误)申请”)
  52. 写了placement new 也要写placement delete(当你写一个placement operator new,请确定也写出了对应的placement operator delete,否则可能会发生隐微而时断时续的内存泄漏;当你声明placement new和placement delete,请确定不要无意识(非故意)地遮掩了它们地正常版本)
  53. 不要轻忽编译器的警告
  54. 让自己熟悉包括TR1 在内的标准程序库(TR1,C++ Technical Report 1,C++11 标准的草稿文件)
  55. 让自己熟悉Boost(准标准库)

More Effective c++

  1. 仔细区别pointers 和references(当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或是当你实现一个操作符而其语法需求无法由pointers 达成,你就应该选择references;任何其他时候,请采用pointers)
  2. 最好使用C++转型操作符(static_castconst_castdynamic_castreinterpret_cast
  3. 绝不要以多态(polymorphically)方式处理数组(多态(polymorphism)和指针算术不能混用;数组对象几乎总是会涉及指针的算术运算,所以数组和多态不要混用)
  4. 非必要不提供default constructor(避免对象中的字段被无意义地初始化)
  5. 对定制的“类型转换函数” 保持警觉(单自变量constructors 可通过简易法(explicit 关键字)或代理类(proxy classes)来避免编译器误用;隐式类型转换操作符可改为显式的member function 来避免非预期行为)
  6. 区别increment/decrement 操作符的前置(prefix)和后置(postfix)形式(前置式累加后取出,返回一个reference;后置式取出后累加,返回一个const 对象;处理用户定制类型时,应该尽可能使用前置式increment;后置式的实现应以其前置式兄弟为基础)
  7. 千万不要重载&&||,操作符(&&||的重载会用“函数调用语义”取代“骤死式语义”;,的重载导致不能保证左侧表达式一定比右侧表达式更早被评估)
  8. 了解各种不同意义的new和delete(new operatoroperator newplacement newoperator new[]delete operatoroperator deletedestructoroperator delete[]
  9. 利用destructors 避免泄漏资源(在destructors 释放资源可以避免异常时的资源泄漏)
  10. 在constructors 内阻止资源泄漏(由于C++ 只会析构已构造完成的对象,因此在构造函数可以使用try...catch 或者auto_ptr(以及与之相似的classes) 处理异常时资源泄露问题)
  11. 禁止异常流出destructors 之外(原因:一、避免terminate 函数在exception 传播过程的栈展开(stack-unwinding)机制种被调用;二、协助确保destructors 完成其应该完成的所有事情)
  12. 了解“抛出一个exception” 与“传递一个参数” 或“调用一个虚函数” 之间的差异(第一,exception objects 总是会被复制(by pointer 除外),如果以by value 方式捕捉甚至被复制两次,而传递给函数参数的对象则不一定得复制;第二,“被抛出成为exceptions” 的对象,其被允许的类型转换动作比“被传递到函数去” 的对象少;第三,catch 子句以其“出现于源代码的顺序” 被编译器检验对比,其中第一个匹配成功者便执行,而调用一个虚函数,被选中执行的是那个“与对象类型最佳吻合”的函数)
  13. 以by reference 方式捕获exceptions(可避免对象删除问题、exception objects 的切割问题,可保留捕捉标准exceptions 的能力,可约束exception object 需要复制的次数)
  14. 明智运用exception specifications(exception specifications 对“函数希望抛出什么样的exceptions” 提供了卓越的说明;也有一些缺点,包括编译器只对它们做局部性检验而很容易不经意地违反,与可能会妨碍更上层的exception 处理函数处理未预期的exceptions)
  15. 了解异常处理的成本(粗略估计,如果使用try 语句块,代码大约整体膨胀5%-10%,执行速度亦大约下降这个数;因此请将你对try 语句块和exception specifications 的使用限制于非用不可的地点,并且在真正异常的情况下才抛出exceptions)
  16. 谨记80-20 法则(软件的整体性能几乎总是由其构成要素(代码)的一小部分决定的,可使用程序分析器(program profiler)识别出消耗资源的代码)
  17. 考虑使用lazy evaluation(缓式评估)(可应用于:Reference Counting(引用计数)来避免非必要的对象复制、区分operator[] 的读和写动作来做不同的事情、Lazy Fetching(缓式取出)来避免非必要的数据库读取动作、Lazy Expression Evaluation(表达式缓评估)来避免非必要的数值计算动作)
  18. 分期摊还预期的计算成本(当你必须支持某些运算而其结构几乎总是被需要,或其结果常常被多次需要的时候,over-eager evaluation(超急评估)可以改善程序效率)
posted @ 2020-03-25 15:29  Xu_Lin  阅读(203)  评论(0编辑  收藏  举报