轻量级C ++编码标准中最重要的元素

时间:2008-10-28 10:03:23

标签: c++ coding-style

我参与了开发非常精细的编码标准。我自己的经验是,如果你没有适当的流程来维护它以及维护它的策略,那就很难执行。

现在,我正在努力并领导一个环境,即在不久的将来拥有流程和后续策略的可能性更小。我仍然希望维持一些最低级别的可敬代码。所以我认为我会在这里得到很好的建议,我们可以共同制作一个合理的轻量级子集,其中最重要的编码标准实践供其他人用作参考。

所以,要强调这里的本质:

C ++编码标准的哪些元素最重要?

  • 回答/投票规则

    • 每个答案有1个候选人,最好有简短的动机。

    • 投票候选人,专注于风格和主观格式指南。这并不是说它们不重要,只是说它们在这种背景下不那么重要。

    • 投票候选人,专注于如何评论/记录代码。这是一个更大的主题,甚至可能应该得到自己的职位。

    • 投票候选人明显有助于提供更安全的代码,从而最大限度地降低了神秘错误的风险,从而提高了可维护性等。

    • 不要向您不确定的候选人投票任何方向。即使它们听起来合理而聪明,或相反“肯定没有人会使用”,你的投票应该基于清晰的理解和经验。

40 个答案:

答案 0 :(得分:68)

首选RAII

STL的自动(以及在boost& C ++ 0x中共享)指针可能有所帮助。

答案 1 :(得分:58)

默认使用const个标识符。它们为读者/维护者提供了保证,并且比以后插入更容易构建。

成员变量和方法都将被声明为const,以及函数参数。 const个成员变量强制正确使用初始化列表。

此规则的副作用:避免有副作用的方法。

答案 2 :(得分:48)

使用C ++强制转换而不是C强制转换

使用:

  • static_cast
  • const_cast
  • reinterpret_cast
  • dynamic_cast

但绝不是C式演员。

它如何明确地促进更安全的代码,从而最大限度地降低了神秘错误的风险,从而提高了可维护性等。

每个演员阵容都有限。例如,如果你想删除一个const(无论出于何种原因),const_cast不会同时改变类型(这可能是一个很难找到的bug)。

此外,这使得审阅者可以搜索它们,然后编码人员在需要时为其辩护。

答案 3 :(得分:40)

尽可能使用引用而不是指针。这可以防止持续的防御性NULL检查。

答案 4 :(得分:36)

确保编译器的警告级别设置得足够高(/ Wall最好),以便它能够捕获如下的愚蠢错误:

if (p = 0)

当你真正意味着

if (p == 0)

这样你就不需要采取像以下那样愚蠢的技巧了:

if (0 == p)

会降低代码的可读性。

答案 5 :(得分:34)

使用向量和字符串而不是C样式数组和char *

每当需要创建数据缓冲区时,使用 std :: vector ,即使大小已修复。

每当需要字符串时,请使用 std :: string

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

std :: vector:向量的用户总能找到它的大小,如果需要,可以调整向量的大小。它甚至可以(通过(&(myVector [0]))表示法给出一个C API。当然,矢量会自行清理。

std :: string:几乎与上述相同的原因。事实上它将始终被正确初始化,它不会被溢出,它将优雅地处理修改,如连接,分配等等,并以自然的方式(使用运算符而不是函数)

答案 6 :(得分:32)

将功能保持在合理的大小。就个人而言,我喜欢将功能保持在25行以下。当您可以将功能作为一个单元进行操作而不必上下扫描以确定其工作原理时,可读性得到增强。如果你必须滚动阅读它,那就更糟了。

答案 7 :(得分:31)

assert所有假设,包括临时假设,如未实现的行为。如果不重要,则断言函数进入和退出条件。断言所有重要的中间状态。如果没有断言失败,你的程序永远不会崩溃。您可以自定义断言机制以忽略将来的出现。

  

对预期发生的情况使用错误处理代码;将断言用于永不发生的条件。错误处理通常会检查错误的输入数据;断言检查代码中的错误。

     

如果使用错误处理代码来解决异常情况,则错误处理将使程序能够正常响应错误。如果针对异常情况触发了断言,则纠正措施不仅仅是优雅地处理错误 - 纠正措施是更改程序的源代码,重新编译和发布新版本的软件。思考断言的好方法是作为可执行文档 - 你不能依赖它们来使代码工作,但它们可以比程序语言注释更积极地记录假设[1]。

  1. 麦康奈尔,史蒂夫。代码完成,第二版。 Microsoft Press©2004。第8章 - 防御性编程

答案 8 :(得分:29)

知道谁是那个记忆的所有者。

  • 尽可能在堆栈上创建对象(没用无用的新东西)
  • 除非确实需要,否则应避免转让所有权
  • 使用RAII和智能指针
  • 如果要求转让所有权(没有智能指针),那么,清楚地记录代码(函数应该有一个非模糊的名称,总是使用相同的名称模式,如“char * allocateMyString()”和“void” deallocateMyString(char * p)“。

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

没有明确的内存所有权原则导致有趣的错误或内存泄漏,并且时间丢失,想知道此函数返回的char *是否应由用户释放,或者是否返回特殊的释放函数等..

尽可能地,分配内存的函数/对象必须是解除分配它的函数/对象。

答案 9 :(得分:27)

附注:不要施加SESE(单一条目单一退出)(即不要禁止多个return,使用break / {{1} } / ...)

在C ++中,这是一个乌托邦,因为continue是另一个回归点。 SESE在C和无异常语言方面有两个优势:

  • 现在由C ++中的RAII习语巧妙地处理的资源的确定性释放,
  • 使功能更容易维护,这不应该是一个问题,因为必须的功能必须保持简短(由“一个功能,一个责任”的规则指定)

答案 10 :(得分:26)

过早优化是万恶之源

首先编写安全且正确的代码。

然后,如果您遇到性能问题,并且如果您的探查器告诉您代码很慢,您可以尝试对其进行优化。

永远不要相信你会比编译器更好地优化代码片段。

在寻找优化时,请研究所使用的算法,以及可能更好的替代方案。

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

通常,“优化”(或假设优化)的代码不那么清晰,并且倾向于通过原始的,近乎机器的方式表达自己,而不是更注重业务的方式。一些优化依赖于switch,if等,然后由于多个代码路径而更难以测试。

当然,在分析之前进行优化通常会导致性能提升为零。

答案 11 :(得分:23)

任何控制语句的大括号。 (感谢自己的经验,并通过阅读Code Complete v2加强):

// bad example - what the writer wrote
if( i < 0 ) 
    printf( "%d\n", i );
    ++i; // this error is _very_ easy to overlook!  

// good example - what the writer meant
if( i < 0 ) {
    printf( "%d\n", i );
    ++i;
}

答案 12 :(得分:15)

首选符合标准的代码。更喜欢使用标准库。

答案 13 :(得分:12)

只是琐碎的使用了? :运营商,即

float x = (y > 3) ? 1.0f : -1.0f;

没问题,但事实并非如此:

float x = foo(2 * ((y > 3) ? a : b) - 1);

答案 14 :(得分:9)

使用棉绒工具 - 即PC-Lint。这将捕获许多“结构”编码指南问题。意思是读取实际错误而不是样式/可读性问题的东西。 (并不是说可读性不重要,但它不如实际错误那么重要。)

示例,而不是要求这种风格:

if (5 == variable)

作为一种防止“意外分配”错误的方法,让lint找到它。

答案 15 :(得分:9)

永远不要使用没有适当构造函数的结构

结构体是合法的C ++结构,用于将数据聚合在一起。不过,数据应始终正确初始化。

所有C ++结构应该至少有一个默认构造函数,它将聚合数据设置为默认值。

struct MyStruct // BAD
{
   int i ; bool j ; char * k ;
}

struct MyStruct // GOOD
{
   MyStruct() : i(0), j(true), k(NULL) : {}

   int i ; bool j ; char * k ;
}

如果它们通常以某种方式初始化,请提供一个构造函数以使用户能够避免C样式的结构初始化:

MyStruct oMyStruct = { 25, true, "Hello" } ; // BAD
MyStruct oMyStruct(25, true, "Hello") ;      // GOOD

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

没有适当的构造函数的结构使得该结构的用户能够初始化它。因此,以下代码将从函数复制粘贴到函数:

void doSomething()
{
   MyStruct s = { 25, true, "Hello" } ;
  // Etc.
}

void doSomethingElse()
{
   MyStruct s = { 25, true, "Hello" } ;
  // Etc.
}

// Etc.

这意味着,在C ++中,如果需要在结构中添加字段或更改内部数据的顺序,则必须完成所有这些初始化以验证每个字段仍然正确。使用适当的构造函数,修改结构的内部与其使用分离。

答案 16 :(得分:8)

不要向全局命名空间添加类型或函数。

答案 17 :(得分:8)

<强> Principle of least surprise

也许这不是你想要的规则的“味道”,但我绝对会把它放在首位。

不仅是格式化和评论指南等所有无聊内容的根本原因和理智检查,而且 - 更重要的是 - 将重点放在读取和理解的代码上,而不仅仅是编译。

它还涵盖了我遇到的唯一合理的代码质量指标 - WTF's per minute

我将使用第一点来强调清晰,一致的代码的重要性和价值,并激励编码标准中的以下项目。

答案 18 :(得分:6)

禁止t[i]=i++; f(i++,i);,依此类推,因为没有(便携式)保证首先执行的内容。

答案 19 :(得分:5)

始终,始终始终在对象构造上进行正确的数据成员初始化。

我遇到了一个问题,即对象构造函数依赖于其数据成员的某些“默认”初始化。在两个平台(Windows / Linux)下构建代码会产生不同的结果和难以发现的内存错误。结果是数据成员未在构造函数中初始化,并在初始化之前使用。在一个平台(Linux)上,编译器将其初始化为代码编写者认为合适的默认值。在Windows上,值被初始化为某些东西 - 但是垃圾。在使用数据成员时,一切都变得混乱。一旦初始化得到修复 - 不再有问题。

答案 20 :(得分:4)

如果正在使用的工具链(或预计使用)具有例外的低效实现,则避免使用它们可能是明智的。我在这样的条件下工作过。

更新:here是其他人对“嵌入式C ++”的理由,它似乎排除了异常。它提出了以下几点:

  • 很难估计发生异常和控制已经传递到相应的异常处理程序之间的时间。
  • 很难估计异常处理的内存消耗。

该页面上有更详细的文字,我不想全部复制。此外,它已经10年了,所以它可能再也没用了,这就是为什么我把关于工具链的部分包括在内。也许这也应该是“如果内存不被视为主要问题”,和/或“如果不需要可预测的实时响应”,等等。

答案 21 :(得分:4)

公共继承必须建模Liskov替换原则(LSP)。

没有替代性的代码重用/导入必须在非常强的耦合有意义时使用私有继承来实现,否则必须使用聚合来实现。

答案 22 :(得分:4)

小心C API

C API可以非常高效,但需要暴露的原始数据(即指针等),这将无助于代码的安全性。请改用现有的C ++ API,或用C ++代码封装C API。

e.g:

// char * d, * s ;
strcpy(d, s) ; // BAD

// std::string d, s ;
d = s ;        // GOOD

永远不要使用strtok

strtok不是可重入的。这意味着如果一个strtok启动而另一个未结束,则会损坏另一个的“内部数据”。

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

使用C API意味着使用原始类型,当sprintf走得太远时(或者使用snprintf时的字符串裁剪,这是一种数据损坏),这可能导致有趣的错误,如缓冲区溢出(和潜在的堆栈损坏)。即使在处理原始数据时,malloc也很容易被滥用,如下面的代码所示:

int * i = (int *) malloc(25) ; // Now, I BELIEVE I have an array of 25 ints!
int * j = new int[25] ;        // Now, I KNOW I have an array of 25 ints!

等。等。

至于strtok:C和C ++是支持堆栈的语言,它使用户不必关心堆栈上自己的功能,以及在堆栈下面将调用哪些函数。 strtok消除了“不关心”的自由

答案 23 :(得分:4)

默认情况下,避免使用生成的复制构造函数和operator =。

  • 如果您希望对象可以复制。
    • 如果每个属性都可以被轻易复制,请清楚地注释你正在使用隐式复制构造函数和operator =故意。
    • 否则,编写自己的构造函数,使用初始化字段初始化属性并遵循标题顺序(这是真正的构造顺序)。
  • 如果仍然不知道(默认选项)或您认为您不想复制某个类的对象,请声明其复制构造函数和operator = as private。通过这种方式,编译器会在您做某些您不想做的事情时通知您。
    class foo
    {
       //...
    private:
       foo( const foo& );
       const foo& operator=( const foo& );
    };

如果你正在使用boost,那么在cleaner way中:

    class foo : private boost::noncopyable
    {
      ...
    };

答案 24 :(得分:4)

一致命名方案中的方法和变量名称;在阅读源代码时,我不会因为其他任何事情而烦恼。

答案 25 :(得分:2)

可能是一个不用脑子,但却是一个重要的规则:

避免未定义的行为。

在C ++中有很多它,并且编写一个不以某种方式依赖它的重要应用程序可能是不可能的,但一般规则应该仍然是“未定义的行为是坏的”。 (可悲的是,有些C ++程序员认为“它在我的机器/编译器上工作”已经足够了)。

如果你必须依赖它,请向每个人说清楚,为什么,在哪里以及如何做。

答案 26 :(得分:2)

通过const引用传递输入参数,并通过指针传递输出或输出输出参数。这是Google风格指南中的一条规则。

我曾经对指针有绝对的厌恶,并且尽可能使用引用(正如这个帖子中的一个海报所建议的那样)。但是,采用这种输出-arg-as-pointer约定使我的函数更具可读性。例如,

SolveLinearSystem(left_hand_side, right_hand_side, &params);

明确表示正在写入“params”。

答案 27 :(得分:2)

应该专注于解释值语义和实体语义之间的区别。它可以提供有关如何处理副本的典型代码片段。

另见Checklist for writing copy constuctor and assignment operator in C++

答案 28 :(得分:2)

无论采用何种指导原则,都要非常轻松来识别适用性:您选择的选择越少,您选择的时间就越少。并且更容易对代码进行脑力训练。

“难以识别”的例子:

  • 如果条件正文中只有一行
  • ,则没有大括号
  • 对名称空间使用K&amp; R大括号放置,但将大括号置于函数定义代码
  • 中的条件下
  • ...

答案 29 :(得分:1)

我认为编码标准文档不是解决此问题的方法。解决方案是激励你的劳动力学习/关心编码的人性化方面 - “以人为本,计算机最后的代码”。

显然,不可能只解雇那些不关心的人 - 但标准文件也不会对他们有所帮助。

答案 30 :(得分:1)

我建议只要求开发人员阅读一系列指南,以及Meyers的有效C ++和更有效的C ++书籍。

如果你想要轻量级,你将不得不依赖常识和共同的理想。

代码审查也有助于强制执行此操作。

为了保持轻量级,我会避免文件和代码警察。公开赞美良好的代码。

编辑 - 我在这里开始发表评论,但会将其放在响应中以便于查看:

正确完成的评论会产生奇迹 - 但您不能允许将报告层次结构纳入评审,并且评论结果中不会包含人名的统计信息。

确保文档较小,并确保为“规则”/指南提供原因。如果没有那个,那么你只需要盲目的服从。根据理由和理由,您需要进行教育,以便实际发布/写入“规则”变得不必要。 (因为这个概念将被内化)

答案 31 :(得分:1)

如果您有多个缩进步骤,则需要使用大括号:

if (bla) {
  for (int i = 0; i < n; ++i)
    foo();
}

这有助于使缩进与编译器看到代码的方式保持一致。

答案 32 :(得分:1)

将您的编码风格基于“C ++编码标准”(Sutter / Alexandrescu),并且仅记录您偏离它的位置。我不能认真对待任何“C ++编码标准”。

答案 33 :(得分:1)

规则0应始终为:

记录与此标准的任何偏差

某些问题可能需要不遵守特定标准。

这些案例必须需要记录和证明,因为它们表明与代码库中的预期习语有偏差。

不记录它们会导致错误,因为:

  1. 未来的维护者可以在不使用的情况下使代码符合要求 理解为什么它首先是不合规的。

  2. 未来的开发人员可能希望代码符合并制作 关于其含义的错误假设。

答案 34 :(得分:0)

限制您使用的类型

如果您需要使用整数类型,请选择一个并保留它。这将避免与short,int,long等类型混合相关的问题。

// BAD
int i ;
long j ;
short k ;

// GOOD (if you choose the "int" as integer)
int i ;
int j ;
int k ;

实际类型也是如此:选择一个(例如双倍),不要使用另一个。

注意:仍然存在签名/无符号的问题,这不能总是避免,并且事实上STL使用自己的整数类型(即std :: vector :: size_type),但所有剩余的代码都不应该混合。

注意2:您可以使用typedef为有符号整数和实数“选择”您的首选类型。如果需要,这将实现低成本变化。

它如何明确地促进更安全的代码,最大限度地降低了神秘错误的风险,从而提高了可维护性等等?

通过将无符号类型与有符号类型进行比较,神秘的精度损失或整数欠/溢出来创建一些错误。

编译器通常在编译时发送警告,但是,通常的答案是“抛出”警告,这有助于隐藏错误。

修改

plinth做了一个有用的评论我会在这里复制粘贴:

  

编写了大量必须与硬件级别的东西进行交互的代码,我对这个指南说不出多少。对于这个级别的工作,我更喜欢将整数类型抽象为包含精度的名称(即int16,uint16,int32,uint32等) - plinth 8月18日20:50

当然,

基座是对的。有时您必须处理int16,uint8和其他“精确定义”类型。

这不会使上面的帖子无效,只会完成它。

错误的来源是混合不同的类型(例如,将unsigned char转换为int),因此,必须避免这种混合。因此适用以下规则:

  • 选择一个泛型整数类型(例如int),并在处理泛型整数时坚持使用(对于实数也是如此)
  • 如果(且仅当)您需要确切的类型(如uint8或int16),请使用它们
  • 永远不要混用不同的类型。
  • 如果您真的必须混用不同的类型,那么请非常谨慎。

下面是一个破解的代码示例:

void * doAllocate(uint32 i)
{
   // try to allocate an array of "i" integers and returns it
}

void doSomething()
{
   uint32 i0 = 225 ;
   int8   i1 = 225 ;  // Oops...

   doAllocate(i0) ;   // This will try to allocate 255 integers
   doAllocate(i1) ;   // This will TRY TO allocate 4294967265
                      // integers, NOT 225
}

答案 35 :(得分:0)

最好的标准是那些小而且专注于制作高质量代码真正重要的标准。他们不试图教编码,他们不试图强制采用特定的编码方式。它们通常坚持一致性特征和主观评论(例如,如果团队的其他人认为一段代码是可读的,符合一致性规则,并且被评论,那么它总是会成为好的代码)

所以要再次强调:一致性 - 命名约定,空格管理,注释块,目录结构。没有别的真的很重要

为达斯汀编辑: 标准的一个大问题是例外。如果您的标准上写着“每行1个语句”,则不能编写以下的组成示例:

SetColText(1,"col1"); SetColWidth(1, 10);
SetColText(2,"col1"); SetColWidth(2, 10);
...
SetColText(9,"col1"); SetColWidth(9, 10);

但是我会说它更具可读性,因此不容易出错,将它们分开。 (我相信你能提出更好的例子)。

这是我的观点 - 告诉人们如何编写代码,以及如何将其格式化为严格的规则总是会以你没有预料到的方式和地点而落空。因此,在执行一些规则后,最好相信你的程序员可以立即执行。如果他们有一些规则要遵循,他们会编写好的,有纪律的代码,这样你就不需要其他蹩脚的规则了。

您会看到页面和页面的某些标准。 (飞利浦C#one长度为48页!)

因此,鉴于您拥有一支优质编码人员团队,您需要做些什么才能更轻松地使用他们的代码?答案始终是他们放置代码的“位置”的一致性,而不是他们如何编写代码。例如。你总是有一个bin,项目中的obj目录是一个很好的标准。你可以拿起任何项目,知道事情的位置......不像有人在他的c:/ mybin目录下构建他所有的二进制文件,因为对他来说更容易。

答案 36 :(得分:0)

确保将析构函数定义为 虚拟

 class GoodClass {
 public:
   GoodClass();
   virtual ~GoodClass()
 };

 class BadClass {
 public:
   BadClass();
   ~BadClass()
 };

答案 37 :(得分:0)

计算机编程艺术之书{1,2,3}

答案 38 :(得分:-2)

没有标签(允许更好地使用外部/其他工具)和为标签插入的固定空格。

答案 39 :(得分:-7)

按名称对类声明和定义中的函数进行排序。这样可以更轻松地在.cpp文件中找到它们。此外,它让您放心,因为您不必考虑将新功能放在何处。