C++编码规范 读书笔记

发布时间:2016-12-7 3:53:18 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"C++编码规范 读书笔记",主要涉及到C++编码规范 读书笔记方面的内容,对于C++编码规范 读书笔记感兴趣的同学可以参考一下。

      项目组一直没有做代码审查,最近有启动这项计划的打算,因此提前复习一下《C++编程规范》,并做一些笔记。我们做任何事通常都先从简单的入手,循序渐进,持续改进,那么做代码审查也不例外,《C++编程规范》又很多,如果一下子突然引入,会对代码编写提出过高的要求,对开发人员的打击比较大,从而可能会影响团队的整个士气,所以我想我们应该从最简单(即容易遵循做到)、最重要的几个规范开始,即追求 【有效性/复杂性】 最大化。      联想到日程安排的十字表格,如法炮制了如下表格,以便分门别类: 代码审查 B(简单&很重要) C(复杂&很重要) A(简单&较重要) D(复杂&较重要)     当然,规范本没有重要与不重要之分,这里这样给其画上这样的标签,只是一个相对的概念,是给我们推进“代码审查”这项工作一个简单的指引,例如先实施A区的规则,一段时间后,当团队成员都习惯了这些规则,再实施B区的规则,基本上按照先易后难的顺序,依次推进。     由于笔者的学识水平,以及经验所致,对一些规则的认识肯定存在偏差与不妥,还请同学批评赐教。   《C++编程规范——101条规则、准则与最佳实践》(C++ Coding Standards——101 Rules, Guidelines and Best Practices) 组织和策略问题  第0条(D): 不要拘泥于小节(又名:了解哪些东西不应该标准化)       是的,有些东西不应该规定过死。但是我们认为,在一些个人风格和喜好方面,保持团队内部的一致性是有好处的。一致性的重要性似乎怎么强调都不过分。 第1条(B):在高警告级别下干净利落地进行编译       将编译器的警告级别调到最高,高度重视警告。编译器是我们的好朋友,如果它对某个构建发出警告,就说明这个构建可能存在潜在的问题。这个问题可能是良性的,也可能是恶性的。我们应该通过修改代码而不是降低警告级别来消除警告。 第2条(C):使用自动构建系统       “一键构建”,甚至使构建服务器根据代码的提交自动进行构建,持续集成,不断交付软件产品,在迭代中完善。尽管现在有了很多开源的自动化构建系统,但是搭建并维护这样一个自动化构建系统,需要团队中有一位经验丰富的高手。 第3条(B):使用版本控制系统(VCS)        VSS, SVN, GIT。 第4条(*):做代码审查       审查代码:更多的关注有助于提高质量。亮出自己的代码,阅读别人的代码。互相学习,彼此都会受益。代码审查无需太形式主义,但一定要做,团队可根据自己的实际情况尝试着去做,在做的过程中,慢慢改进,找到一个符合团队实际需要的方式。CppCheck 设计风格 第5条(C):一个实体应该只有一个紧凑的职责       一次只解决一个问题:只给一个实体(变量、类、函数、名字空间、模块和库)赋予一个定义良好的职责。随着实体变大,其职责范围自然也会扩大,但是职责不应该发散。 第6条(C):正确、简单和清晰第一       软件简单为美(KISS原则:Keep It Simple Software):质量优于速度;简单优于复杂;清晰优于机巧;安全第一。可读性:代码必须是为人编写的,其次才是计算机。 第7条(C):编程中应该知道何时和如何考虑可伸缩性     面对数据的爆炸性增长,应该集中精力改善算法的O(N)复杂度。在这种情况下,小型的优化(例如节约一个赋值、加法或乘法运算)通常无济于事。 第8条(A):不要进行不成熟的优化 第9条(A):不要进行不成熟的劣化      构造既清晰又有效的程序有两种方式:使用抽象(DIP)和库(STL)。 第10条(B):尽量减少全局和共享数据 第11条(C):隐藏信息 第12条(D):懂得何时和如何进行并发性编程       线程安全?并发编程?加锁解锁死锁?这些对我来说还只是属于概念......,鄙视一下自己! 第13条(B):确保资源为对象所拥有。使用显式的RAII和智能指针      利器在手,不要再徒手为之。当然,也要防止智能指针的过度使用。如果只对有限的代码可见(例如函数内部,类内部),原始指针就够用了。 编程风格 第14条(C):宁要编译时和连接时错误,也不要运行时错误 第15条(A):积极的使用const      const是我们的朋友,不变的值更易于理解,跟踪和分析。定义值的时候,应该将const作为默认选项。用mutable成员变量实现逻辑上的不变性,在给某些数据做缓存处理的时候经常使用这一特性。即在类的const成员函数中可以合法的修改类的mutable成员变量。      当然,对于那种通过值传递的函数参数声明为const 纯属多此一举,反而还会引起误解。 第16条(D):避免使用宏      这似乎是C++中一条人人皆知的编程规范。可真正严格遵守的团队并不多(纯属猜想,哈哈)。 第17条(D):避免使用“魔数”     同第16条。     应该用符号常量替换直接写死的字符串(或宏)。将字符串与代码分开(比如将字符串放入一个独立的CPP文件中),这样有利于审查和更新,而且有助于国家化。 第18条(A):尽可能局部地声明变量     避免作用域膨胀。变量的生存期越短越好。因此,尽可能只在首次使用变量之前声明之(通常这时你也有足够的数据对它初始化了)。 第19条(B):总是初始化变量。     这里有两段很好的示例代码: [cpp] view plaincopy // 虽然正确但不可取的方式:定义变量时没有初始化   int speedupFactor;   if (condition)       speedupFactor = 2;   else       speedupFactor = -1;   以下两种方式更好一些: [cpp] view plaincopy // 可取的方式一:定义变量时即初始化   int speedupFactor = -1;   if (condition)       speedupFactor = 2;        // 较好且简练的方式二:定义变量时即初始化   int speedupFactor = condition ? 2 : -1;     第20条(D):避免函数过长,避免嵌套过深 第21条(C):避免跨编译单元的初始化依赖 第22条(A):尽量减少定义性依赖。避免循环依赖。     尽可能的使用前置声明(forward declaration). Pimpl惯用法对遵循这一规范有实际性的帮助。DIP(依赖倒置原则)。 第23条(A):头文件应该自给自足    各司其责:应该确保每个头文件都能够单独编译。 第24条(A):总是编写内部的#include保护符,决不要编写外部的#include保护符 函数与操作符 第25条(B):正确地选择通过值、(智能)指针或者引用传递参数 第26条(C):保持重载操作符的自然语义 第27条(C):优先使用算术操作符和赋值操作符的标准形式      1)如果要定义 a+b,也应该定义 a+=b。一般利用后者实现前者,即赋值形式的操作符完成实际的工作,非赋值形式的操作符调用赋值形式的操作符。2)如果可能,优先选择将这些操作符函数定义为非成员函数,并将其和要类型T放入同一个名字空间中。3)非成员函数返回值或引用,成员函数返回引用。 帖几段示例代码: [cpp] view plaincopy // 成员函数 @=    T& operator@=(const T& rhs)   {        //.....具体的实现代码.....        return *this;   }      // 非成员函数 @    T  operator@(const T& lhs, const T& rhs)   {   T temp = lhs;   return temp @= rhs;   }      // 非成员函数 @= 返回输入参数的引用   T& operator@=(T& lhs, const T& rhs)   {       // ......具体的实现代码......       return lhs; // 返回输入参数的引用   }      // 非成员函数 @    T operator@(T lhs, const T& rhs)   {       return lhs @= rhs;   }     第28条(A):优先使用++和--的标准形式。优先使用前缀形式       如果定义了++C,也应该定义 C++,而且应该用前缀形式实现后缀形式。 标准形式,示例代码: [cpp] view plaincopy //  前缀形式   T& operator++()   {       // ......执行递增的实现代码      return *this; // 返回递增后的新值   }       // 后缀形式   T operator++(int) // 返回值   {        T oldT(*this); // 先保存原值         ++(*this);  // 调用前缀形式执行递增         return oldT; //  返回原值   }     第29条(D):考虑重载以避免隐含类型转换 第30条(A):避免重载操作符&&, || 和,(逗号) 第31条(A):不要编写依赖于函数参数求值顺序的代码      调用函数时,参数的求值顺序是悬而未定的。 《C++编程规范——101条规则、准则与最佳实践》(C++ Coding Standards——101 Rules, Guidelines and Best Practices) 类的设计与继承 第32条(C):弄清所要编写的是哪种类 第33条(C):用小类代替巨类     分而治之。用类表示概念。 第34条(B):用组合代替继承      即优先使用委托而非继承。 第35条(B):避免从并非要设计成基类的类中继承     本意是要独立使用的类所遵守的设计蓝图和基类大不相同,将独立类用作基类是一种严重的设计错误。 第36条(C):优先提供抽象接口     偏爱抽象艺术吧。抽象接口是完全由(纯)虚函数构成的抽象类,没有状态(即没有成员数据),通常也没有成员函数实现。注意:在抽象接口中避免使用状态能够简化整个层次结构的设计。     依赖倒置原理(DIP):1)高层模块不应该依赖低层模块。相反,两者都应该依赖于抽象。2)抽象不应该依赖细节(实现)。相反,细节应该依赖抽象。通常说的“要面向接口编程,而不要面向实现编程”也是这个意思。 第37条(C):公有继承即可替换性。继承,不是为了重用,而是为了被重用     继承塑模的是“是一个(is a kind of )”关系【Liskov替换原则】。组合塑模的是“有一个(has a kind of )”关系。 第38条(D):实施安全的覆盖 第39条(D):考虑将虚拟函数声明为非公有的,将公有函数声明为非虚拟的      在基类中进行修改代价是高昂的(尤其对于框架或库)。非虚拟接口模式(NonVirtual Interface, NVI)。 第40条(A):要避免提供隐式转换      explicit构造函数和命名的转换函数。 第41条(A):将数据成员设为私有的,无行为的聚集除外(即C语言形式的struct)      保护数据具有公有数据的所有缺点。可以考虑使用Pimpl惯用法用来隐藏类的私有数据成员。 第42条(C):不要公开内部数据      隐藏数据却又暴露句柄是一种自欺欺人的做法,就像锁上了自家的门,却把钥匙留在了锁上。 第43条(D):明智的使用Pimpl惯用法 [cpp] view plaincopy // 将类的私有数据隐藏在一个不透明的指针后面   class Map   {    public:      // .... 接口   private:         struct PrivateImpl; // 类Map的嵌套类型        shared_ptr<PrivateImpl> m_Impl;   };   第44条(D):优先编写非成员非友元函数            要避免较成员费:尽可能优先指定为非成员非友元函数。1)减少依赖;2)分离巨类;3)提供通用性。 第45条(D):总是一起提供new和delete 第46条(D):如果提供类专门的new,应该提供所有的标准形式(普通,就地和不抛出) 构造、析构与复制 第47条(A):以同样的顺序定义和初始化成员变量             如果违反了该条规则,也会违反第1条 在高警告级别下干净利落地进行编译。 第48条(A):在构造函数中用初始化代替赋值             这可不是不成熟的优化,这是在避免不成熟的劣化。 第49条(B):避免在构造函数和析构函数中调用虚拟函数              这一点其实很好理解:因为在构造期间,对象还是不完整的,如果在基类的构造函数中调用了虚拟函数,那么调用的将是基类的虚拟函数(不管派生类是否对该虚拟函数进行了改写)。C++标准为什么这样?试想:如果调用的是派生类改写后的虚拟函数版本,那么会发生什么事情?派生类改写该虚拟函数势必会调用派生类的成员数据吧?而在构造基类期间,派生类的数据成员还没有被初始化,使用了未初始化的数据,正是通往未定义行为的快速列车。 [cpp] view plaincopy // 使用工厂函数插入“后构造函数”调用   class B   {     protect:      B() {/*.... */ }      virtual void PostInitialize() {/*....*/}     public:       template<typename T>      static shared_ptr<T> create() // 函数模板      {        shared_ptr<T> p(new T);        p->PostInitialize();        return p;      }   };      class D : public B   {   // ....   };      shared_ptr<D> pD = D::create<D>();  // 创建一个D的对象   第50条(A):将基类析构函数设为公用且虚拟的,或者保护且非虚拟的 第51条(D):析构函数、释放和交换绝对不能失败 第52条(D):一致地进行复制和销毁            通常,拷贝构造函数,复制赋值操作符函数,析构函数要么都定义,要么都不定义。 第53条(D):显示地启用或者禁止复制 第54条(D):避免切片。在基类中考虑用克隆代替复制        将基类的拷贝构造函数声明为受保护的protected, 这样就不能将派生类对象直接传递给接收基类对象的函数,从而防止了对象切片。取而代之在基类中增加一个克隆函数clone()的定义,并采用NVI模式实现。在公有的非虚拟接口clone()函数中采用断言检查继承自基类的所有派生类是否忘记了重写virtual B *doClone()。 第55条(D):使用赋值的标准形式 第56条(D):只要可行,就提供不会失败的swap()(而且要正确的提供) 模板与泛型 第64条(C):理智的结合静态多态性和动态多态性        动态多态性是以某些类的继承体系出现的,通过虚拟函数和(指向继承层次中的对象的)指针或引用来实现的。静态多态性则是通过类模板和函数模板实现。 第65条(D):有意的进行显示自定义 第66条(D):不要特化函数模板 第67条(D):不要无意地编写不通用的代码 STL:容器 第76条(A):默认时使用vetor。否则,选择其他合适的容器 第77条(B):从vector和string代替数组 第78条(A):使用vector和string::c_str与非C++的API交换数据        vector的存储区总是连续的;大多数标准库对string的实现,也使用连续内存区(但是不能得到保证)。string::c_str总是返回一个空字符'\0'结束的C风格字符串。string::data也是返回指向连续内存的指针,但不保证以空字符'\0'结束。 第79条(D):在容器中只存储值和智能指针 第80条(B):用push_pack代替其他扩展序列的方式 第81条(D):多用范围操作,少用单元素操作 第82条(D):使用公认的惯用法真正的压缩容量,真正的删除元素 container<T>(c).swap(c); // 去除多余容量的 shrink-to-fit惯用法container<T>().swap(c); // 清空容器c c.erase(remove(c.begin(), c.end(), value), c.end()); // 删除容器c中所有等于value的元素, erase-remove惯用法 STL:算法 算法即循环——只是更好。算法是循环的模式。开始使用算法,也就意味着开始使用函数对象和谓词。 第83条(D):使用带检查的STL实现       什么是带检查的STL实现? 第84条(C):用算法调用代替手工编写的循环       有意识的熟悉,使用STL算法吧。 第85条(C):使用正确的STL查找算法       find/find_if, count/count_if, binary_search, lower_bound, upper_bound, equal_range 第86条(C):使用正确的STL排序算法       partition, stable_partition, nth_element, partial_sort, partial_sort_copy, sort, stable_sort 第87条(C):使谓词成为纯函数 第88条(C):算法和比较器的参数应多用函数对象少用函数 第89条(D):正确编写函数对象         模板与泛型编程,C++标准模板库STL一直是自己很薄弱的地方,因为在工作中很少使用。这一来是因为自己起初就对这一块不熟悉,进而导致编程时很少使用(都不知道用有哪些功能啊),而越是这样,使用得越少,就更没有机会是熟悉STL,正是形成一个循环。STL有很多的实用功能,以后要有意识的加以使用,学习,争取掌握它。 名字空间与模块 第57条(D):将类型及其非成员函数接口置于同一名字空间中 第58条(D):应该将类型和函数分别置于不同的名字空间中,除非有意想让他们一起工作        ADL(参数依赖查找,也成Koeing查找)。       关于57条和58条,在机器上实验了一下,没发现啥问题呀。??? 第59条(A):不要在头文件中或者#include之前编写名字空间using        名字空间 using 是为了使我们更方便,而不是让我们用来叨扰别人的:在 #include 之前,绝对不要编写 using 声明或者 using 指令。        推论:在头文件中,不要编写名字空间级的 using 指令或者 using 声明,相反应该显式地用名字空间限定所有的名字。(第二条规则是从第一条直接得出的,因为头文件无法知道以后其他头文件会出现什么样的 #include 。) 简而言之:可以而且应该在实现文件中的 #include 指令之后自由地使用名字空间级的 using 声明和指令,而且会感觉良好。 第60条(D):要避免在不同的模块中分配和释放内存 第61条(A):不要在头文件中定义具有链接的实体 [cpp] view plaincopy // 不要在头文件中定义具有外部链接的实体   int fudgeFactor;   std::string hello("hi, lcz");   void foo() { std::cout << "lcz" << std::endl; }   而解决方法也很简单,应该像如下只在头文件中声明: [cpp] view plaincopy extern int fudgeFactor;   extern std::string hello;   void foo(); // extern 对函数的声明是可有可无的   在实现文件中定义: [cpp] view plaincopy int fudgeFactor;   std::string hello("hi, lcz");   void foo() { std::cout << "lcz" << std::endl; }   同样的,以下在头文件中定义名字空间级的static实体是更危险的行为(因为链接器通常不会报错): [cpp] view plaincopy static int fudgeFactor;   static std::string hello("hi, lcz");   static void foo() { std::cout << "lcz" << std::endl; }   第62条:不要允许异常跨越模块边界传播 第63条(D):在模块的接口中使用具有良好可移植性的类型 错误处理与异常 第68条(B):广泛地使用断言记录内部假设和不变式      断言的强大怎么高估都不算过分。1)按照信息论的原理,一个事件中所包含的信息量与该事件发生的概率是成反比的。因此,如果assert触发的可能性越低,它触发时所提供的信息量就越大。2)避免使用assert(false),应该使用assert( !"information message" ); // 这样有一个很有用的好处,可以取代注释。3)断言是用来报告程序员的错误的,因此,不要使用断言报告运行时的错误。 第69条:建立合理的错误处理策略,并严格遵守 第70条:区别错误与非错误 第71条:设计和编写错误安全代码 第72条:优先使用异常报告错误 第73条:通过值抛出,通过引用捕获 第74条:正确地报告、处理和转换错误 第75条:避免使用异常规范     错误处理与异常这一块在项目中实践得很肤浅,没有什么心得与体会。一般都是遇到错误了即返回,抛出错误日志。 类型安全 第90条(C):避免使用类型分支,多使用多态 第91条(C):依赖类型,而非其表示方式 第92条(A):避免使用reinterpret_cast      如果需要在不相关的指针类型之间强制转换,应该通过void进行转换,不要直接使用reinterpret_cast. 例如: [cpp] view plaincopy T1 *p1 = ...   T2 *p2 = reinterpret<T2*>(p1); // 糟糕!    应该写成: [cpp] view plaincopy T1 *p1 = ....   void *p = p1; // 先用void*指向p1指向的内存区   T2 *p2 = static_cast<T2>(p);     第93条(A):避免对指针使用static_cast 第94条(A):避免强制转换const(const_cast) 第95条(D):不要使用C风格的强制转换 第96条(D):不要对非POD进行 memcpy 和 memcmp 操作 第97条(C):不要使用联合(union)重新解释表示方式      有两种方式是可以接受的:1)读取最后写入的字段(在一些接口需要统一参数而实际参数类型又不相同的时候,经常使用这一招);2)如果两个POD是一union的成员,而且均以相同的字段类型开始,那么对这种匹配的字段来说,写入其中一个而读取另一个是合法的。 第98条(A):不要使用可变长参数(...)      就算要用,我也不会编写。呵呵 第99条(B):不要使用失效对象。不要使用不安全函数     不要使用不安全的C语言遗留函数:strcpy, strncpy, sprintf等C函数。 第100条(B):不要多态地处理数组     1)避免使用数组,而应该使用vector; 2)不要在数组或vector中存储多态的值对象,而应该存储对象指针(最好是智能指针)。

上一篇:[置顶] Flume-0.9.4和Hbase-0.96整合(1)
下一篇:CopyFile函數

相关文章

相关评论