LLVM Coding Standards(LLVM编码规范)

[TOC]

Introduction(简介)

This document attempts to describe a few coding standards that are being used in the LLVM source tree. Although no coding standards should be regarded as absolute requirements to be followed in all instances, coding standards are particularly important for large-scale code bases that follow a library-based design (like LLVM).

【译】本文描述了一些LLVM源码库中正在使用的编码规范。虽说并不存在可以完美适用于所有场景的编码规范,但编码规范对大规模代码工程还是比较重要的,尤其是做类库设计(如LLVM)。

While this document may provide guidance for some mechanical formatting issues, whitespace, or other “microscopic details”, these are not fixed standards. Always follow the golden rule:

If you are extending, enhancing, or bug fixing already implemented code, use the style that is already being used so that the source is uniform and easy to follow.

【译】虽然本文对一些纯格式问题(如空格等小细节)提供了指导,但这并不是一成不变的标准。往往遵循以下黄金准则:

如果在存量代码中进行扩展、增强或问题修复,遵循存量代码的编码风格。这样保证了源码风格统一,大家更容易去遵循。

Note that some code bases (e.g. libc++) have really good reasons to deviate from the coding standards. In the case of libc++, this is because the naming and other conventions are dictated by the C++ standard. If you think there is a specific good reason to deviate from the standards here, please bring it up on the LLVM-dev mailing list.

【译】请注意,有一些代码库有比较好的理由不遵从该编码规范,如libc++,其命名以及其他约定由C++标准所制定。如果你有一个明确的好理由需要违反该编码规范,请在LLVM-dev邮件列表中提出。

There are some conventions that are not uniformly followed in the code base (e.g. the naming convention). This is because they are relatively new, and a lot of code was written before they were put in place. Our long term goal is for the entire codebase to follow the convention, but we explicitly do not want patches that do large-scale reformatting of existing code. On the other hand, it is reasonable to rename the methods of a class if you’re about to change it in some other way. Just do the reformatting as a separate commit from the functionality change.

【译】代码库中,有一些约定(如命名规则)并没有统一遵守,这是因为在它们之前就已经编写了大量代码,它们的添加相对较新。我们的长期目标肯定是使整个代码库都遵循该规范,但我们也不希望在补丁包中做大量的基础代码重构。另一方面,如果你想以其他方式重命名类成员函数,这是合理的,但需要将重构代码单独提交,请勿和功能代码混在一起。

The ultimate goal of these guidelines is to increase the readability and maintainability of our common source base. If you have suggestions for topics to be included, please mail them to Chris.

【译】这些指导的最终目标是提升我们公共代码库的可读性和可维护性。如果你有些新增项的建议,请给Chris邮件。

【注】编码规范大致可以分为格式规范与安全规范两类,而安全规范又可以分为功能性规范与攻击防护型规范。格式规范往往遵从工作模块的整体风格,其注重团队成员编码和阅读无障碍,并不是所有规范都有完善的理由,更倾向于公约;功能性规范关注在编程语言使用上的陷阱,由于编程语言设计的通用性、应用场景的扩张、软硬件的革新等等原因,一些写法或组合更容易导致功能性问题,或在可扩展性可阅读性等方面有更好的设计,这类规范的理由比较充分;攻击防护型规范指的是编程语言设计的缺陷,或应用场景的限制,如脚本注入、不安全随机数等。

Languages, Libraries, and Standards(语言、库和标准)

Most source code in LLVM and other LLVM projects using these coding standards is C++ code. There are some places where C code is used either due to environment restrictions, historical restrictions, or due to third-party source code imported into the tree. Generally, our preference is for standards conforming, modern, and portable C++ code as the implementation language of choice.

【译】LLVM以及使用该编码规范的其他LLVM工程,其大部分源码均是用C++编写,但由于环境限制、历史原因或第三方源码库的导入,目前仍存在一些C代码。通常来说,我们更倾向于把符合规范的、可移植的C++代码作为实现语言的首选。

C++ Standard Versions(C++标准版本)

LLVM, Clang, and LLD are currently written using C++11 conforming code, although we restrict ourselves to features which are available in the major toolchains supported as host compilers. The LLDB project is even more aggressive in the set of host compilers supported and thus uses still more features. Regardless of the supported features, code is expected to (when reasonable) be standard, portable, and modern C++11 code. We avoid unnecessary vendor-specific extensions, etc.

【译】虽然我们仅限于使用主流编译器支持的核心工具链所使用的C++特性,但当前LLVM、Clang和LLD均使用C++实现。LLDB工程在使用各主流编译器支持的特性上,显得更为激进,它使用了更多的C++特性。除了已支持的特性,在合理的情况下,代码都应该由标准的、可移植的C++11编写。我们不会接纳非必要的、小众特化的扩展。

C++ Standard Library(C++标准库)

Use the C++ standard library facilities whenever they are available for a particular task. LLVM and related projects emphasize and rely on the standard library facilities for as much as possible. Common support libraries providing functionality missing from the standard library for which there are standard interfaces or active work on adding standard interfaces will often be implemented in the LLVM namespace following the expected standard interface.

【译】只要对特定任务可用,请使用C++标准库。LLVM以及相关项目都尽可能的依赖于标准库。对于公共库中缺失的功能,如果有标准接口或待添加标准接口,那么通常会在LLVM命名空间下,按照期望的标准接口实现。

There are some exceptions such as the standard I/O streams library which are avoided. Also, there is much more detailed information on these subjects in the LLVM Programmer’s Manual.

【译】也有一些例外,如标准库中的I/O流操作是被禁用的。另外,对于这块更多的详情可参考《LLVM Programmer’s Manual》。

Supported C++11 Language and Library Features(支持的C++11语言与库特性)

While LLVM, Clang, and LLD use C++11, not all features are available in all of the toolchains which we support. The set of features supported for use in LLVM is the intersection of those supported in the minimum requirements described in the Getting Started with the LLVM System page, section Software. The ultimate definition of this set is what build bots with those respective toolchains accept. Don’t argue with the build bots. However, we have some guidance below to help you know what to expect.

【译】虽然LLVM、Clang和LLD均使用C++11,但并不是所有C++11特性都可用于我们支持的全部工具链。LLVM可用的C++11特性集是《Getting Started with the LLVM System》中Software章节所描述的最小需求的交集。这个特性集的最终确立来源于各工具链所接纳特性集的自动构建。一切以自动构建为准。同时,我们下面有一些指导来帮助了解哪些特性是可用的。

Each toolchain provides a good reference for what it accepts:

【译】每个工具链均对其接纳的特性提供了很好地参考:

In most cases, the MSVC list will be the dominating factor. Here is a summary of the features that are expected to work. Features not on this list are unlikely to be supported by our host compilers.

【译】大多数情况下,MSVC列表将是主导因素。以下是可用特性的概要信息,除此之外的特性并不太可能被我们主流编译器所支持。

  • Rvalue references: N2118

    • But not Rvalue references for *this or member qualifiers (N2439)

【注】右值引用:除*this和成员修饰符(ref-qualifiers)的右值引用

【注】静态断言:static_assert,对常量表达式和模板参数在编译期进行检查

【注】自动类型推导:auto关键字

  • Trailing return types: N2541

【注】尾置返回类型:lambda返回值类型表示,以及解决通过decltype推导依赖输入的返回值类型时,编译器尚未捕获输入类型的缺陷。

Trailing return type, useful if the return type depends on argument names, such as

1
2
template <class T, class U>
auto add(T t, U u) -> decltype(t + u);

or is complicated, such as in

1
auto fpif(int)->int(*)(int)
  • Lambdas: N2927

    • But not lambdas with default arguments.

【注】lambda表达式:除默认参数上的lambda表达式***(存疑)***

A lambda-expression appearing in a default argument shall not implicitly or explicitly capture any entity. [ Example:

1
2
3
4
5
6
7
8
void f2() {
  int i = 1;
  void g1(int = ([i]{ return i; })());			// ill-formed
  void g2(int = ([i]{ return 0; })());			// ill-formed
  void g3(int = ([=]{ return i; })());			// ill-formed
  void g4(int = ([=]{ return 0; })());			// OK
  void g5(int = ([]{ return sizeof i; })());	// OK
}

—end example ]

  • decltype: N2343
  • Nested closing right angle brackets: N1757

【注】模板右尖括号嵌套:c98版本模板中>>会被解析为右移,需要添加空格隔开> >,C++11开始支持叠用

【注】外部模板:非模板函数的extern声明关键字,扩展到模板函数

【注】模板特化场景下,0NULL作为空指针时,推导类型为int而非*,导致匹配错误。

  • Strongly-typed and forward declarable enums: N2347, N2764

【注】强类型枚举:enum class代替enum可解决同命名空间下不同枚举类型拥有相同枚举成员名会报错的问题;C++03版本枚举类型视为整数,可与整数、其他枚举类型比较和互转,强枚举类型限制了这种不安全的隐式转换。

  • Local and unnamed types as template arguments: N2657

【译】局部类型和匿名类型作为模板实参

  • Range-based for-loop: N2930

    • But {} are required around inner do {} while() loops. As a result, {} are required around function-like macros inside range-based for loops.

【注】基于范围的for循环:内部使用do {} while()时,需要添加{}。同样,基于范围的for循环内部的函数式宏也需要{}包围。

【注】override通过编译检查,确保子类虚函数重写基类虚函数,若子类与基类虚函数不一致,则编译报错;final标记在类上时,该类无法被继承,final标记在虚函数上,虚函数无法被子类重写,否则编译报错。

  • Atomic operations and the C++11 memory model: N2429

【注】原子操作与C++11内存模型:并发与线程安全

  • Variadic templates: N2242

【译】变参模板

  • Explicit conversion operators: N2437

【注】显示类型转换运算符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct B {
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};
B b1 = 1;      // 错误:复制初始化不考虑 B::B(int)
B b2(2);       // OK:直接初始化选择 B::B(int)
B b3 {4, 5};   // OK:直接列表初始化选择 B::B(int, int)
B b4 = {4, 5}; // 错误:复制列表初始化不考虑 B::B(int,int)
B b5 = (B)1;   // OK:显式转型进行 static_cast
if (b2);       // OK:B::operator bool()
bool nb1 = b2; // 错误:复制初始化不考虑 B::operator bool()
bool nb2 = static_cast<bool>(b2); // OK:static_cast 进行直接初始化
  • Defaulted and deleted functions: N2346

【注】默认函数:default关键字;删除函数:delete关键字

【译】初始化列表

  • Delegating constructors: N1986

【注】委托构造函数:一个类的构造函数可以通过调用它的其他构造进行初始化,但其自身不能再初始化成员变量。

  • Default member initializers (non-static data member initializers): N2756

    • Feel free to use these wherever they make sense and where the = syntax is allowed. Don’t use braced initialization syntax.

【注】默认成员初始化(非静态成员初始化):成员初始化列表优先级高于默认成员初始化。此场景下,请勿使用大括号初始化语法C c4 = C{};

The supported features in the C++11 standard libraries are less well tracked, but also much greater. Most of the standard libraries implement most of C++11’s library. The most likely lowest common denominator is Linux support. For libc++, the support is just poorly tested and undocumented but expected to be largely complete. YMMV. For libstdc++, the support is documented in detail in the libstdc++ manual. There are some very minor missing facilities that are unlikely to be common problems, and there are a few larger gaps that are worth being aware of:

【译】C++11标准库中支持的特性跟踪的不太好,而且这个量还比较大。大多数标准库实现了大部分C++11库中的特性。目测Linux支持的特性可能是最少的。对于libc++,支持的特性也缺少测试和文档,但预计大部分会完善。对于libstdc++,其中手册中详细记录支持的特性。有一些非常小的缺失特性,这不太可能是普遍的问题,倒是有一些大的缺口需要特别注意:

  • Not all of the type traits are implemented

【译】不是所有类型萃取都已实现

  • No regular expression library.

【译】无正则表达式库

  • While most of the atomics library is well implemented, the fences are missing. Fortunately, they are rarely needed.

【译】大多数原子库都实现的很好,但栅栏(约束内存乱序)相关内容缺失了。幸运的是,他们极少被用到。

  • The locale support is incomplete.

【译】系统区域设置(国家或地区设置)的支持不完善。

Other than these areas you should assume the standard library is available and working as expected until some build bot tells you otherwise. If you’re in an uncertain area of one of the above points, but you cannot test on a Linux system, your best approach is to minimize your use of these features, and watch the Linux build bots to find out if your usage triggered a bug. For example, if you hit a type trait which doesn’t work we can then add support to LLVM’s traits header to emulate it.

【译】除了这些点,你应该认为标准库是可用的,而且会按照预期工作,直到某些构建工具提示错误。如果你对上述某个点不确认,而且无法在Linux系统上进行测试,那最好少用这些特性,以及通过Linux构建工具来找出你的使用是否触发了缺陷。

Other Languages(其他语言)

Any code written in the Go programming language is not subject to the formatting rules below. Instead, we adopt the formatting rules enforced by the gofmt tool.

【译】Go语言编写的代码不受以下格式限制。作为替代,我们选用gofmt工具强制执行格式规范。

Go code should strive to be idiomatic. Two good sets of guidelines for what this means are Effective Go and Go Code Review Comments.

【译】Go代码应该尽量符合其语言习惯。《Effective Go》和《Go Code Review Comments》对Go语言都是很好的指导 。

Mechanical Source Issues

Source Code Formatting(源码格式)

Commenting(注释)

Comments are one critical part of readability and maintainability. Everyone knows they should comment their code, and so should you. When writing comments, write them as English prose, which means they should use proper capitalization, punctuation, etc. Aim to describe what the code is trying to do and why, not how it does it at a micro level. Here are a few critical things to document:

【译】注释是可读性和可维护性的关键组成部分。每个人都知道他们应该给代码添加注释,你也是。像写英语散文一样去写注释,这也意味着注释应该使用适当的大写、标点等。**着重描述代码在做什么、为什么这么做,而不是微观层面的代码是怎么实现的。**以下是一些比较重要的内容,需要添加注释:

File Headers(文件头)

Every source file should have a header on it that describes the basic purpose of the file. If a file does not have a header, it should not be checked into the tree. The standard header looks like this:

每个源文件都应该有一个描述其基础用途的文件头注释。如果没有文件头注释,那么该文件不应该添加进代码分支中。标准文件头注释格式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//===-- llvm/Instruction.h - Instruction class definition -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the declaration of the Instruction class, which is the
/// base class for all of the VM instructions.
///
//===----------------------------------------------------------------------===//

A few things to note about this particular format: The “-*- C++ -*-” string on the first line is there to tell Emacs that the source file is a C++ file, not a C file (Emacs assumes .h files are C files by default).

【译】关于这种特殊格式需要注意:第一行的“-*- C++ -*-”用于告知Emacs这是个C++文件,而非C文件(Emacs默认.h为C文件)。

Note(注意)

This tag is not necessary in .cpp files. The name of the file is also on the first line, along with a very short description of the purpose of the file. This is important when printing out code and flipping though lots of pages.

【译】.cpp文件中无需这个标记。文件名以及很短的文件用途描述也写在第一行,这在打印代码并且要翻阅很多页面时非常重要。

The next section in the file is a concise note that defines the license that the file is released under. This makes it perfectly clear what terms the source code can be distributed under and should not be modified in any way.

【译】文件的下一段是对文件发布许可的简明注释。这清楚的表明了,在什么条件下源码可以传播,以及不应以任何方式进行修改。

The main body is a doxygen comment (identified by the /// comment marker instead of the usual //) describing the purpose of the file. The first sentence (or a passage beginning with \brief) is used as an abstract. Any additional information should be separated by a blank line. If an algorithm is being implemented or something tricky is going on, a reference to the paper where it is published should be included, as well as any notes or gotchas in the code to watch out for.

【译】主体部分以doxygen注释格式(用///注释标记而非//)描述了该文件的目的。第一句(以\brief开头的段落)作为摘要,任何其他信息应以空行分隔。如果有正在实现的算法或正在处理的棘手问题,则需要包含相关论文的引用,以及代码注意项或陷阱。

Class overviews(类的概述)

Classes are one fundamental part of a good object oriented design. As such, a class definition should have a comment block that explains what the class is used for and how it works. Every non-trivial class is expected to have adoxygen comment block.

【译】类是一个良好面向对象设计的基础部分。因此,类定义需要包含一个注释块,用于解释类的用途以及工作原理。每个重要类都需要有doxygen注释块。

Method information(方法说明)

Methods defined in a class (as well as any global functions) should also be documented properly. A quick note about what it does and a description of the borderline behaviour is all that is necessary here (unless something particularly tricky or insidious is going on). The hope is that people can figure out how to use your interfaces without reading the code itself.

【译】类成员方法(以及任何全局函数)也应该被恰当的注释。此处仅需要一个阐述其作用的简要注释以及对其边界行为的描述(除非发生一些特别困难的或有风险的事情)。期望达到这样的效果:使用者在不阅读代码实现细节的情况下就能弄清楚如何使用你的接口。

Good things to talk about here are what happens when something unexpected happens: does the method return null? Abort? Format your hard disk?

【译】这里最好讨论一些异常行为的反馈:方法是否返回空值?是否会导致程序退出?是否会格式化硬盘?

Comment Formatting(注释的格式)

In general, prefer C++ style comments (// for normal comments, /// for doxygen documentation comments). They take less space, require less typing, don’t have nesting problems, etc. There are a few cases when it is useful to use C style (/* */) comments however:

【译】通常,我们倾向于C++风格的注释(//用于普通注释,///用于doxygen文档注释)。这种注释风格占用比较少的空间、不需要打太多的字,也没有嵌套问题等等。但也有些情况C风格的注释(/* */)是比较有用的:

  1. When writing C code: Obviously if you are writing C code, use C style comments.

【译】写C代码时:这个很好理解,写C代码时用C风格的注释。

  1. When writing a header file that may be #included by a C source file.

【译】头文件:可能被C源文件包含时。

  1. When writing a source file that is used by a tool that only accepts C style comments.

【译】源文件:被一个仅支持C风格注释的工具所使用时。

  1. When documenting the significance of constants used as actual parameters in a call. This is most helpful for bool parameters, or passing 0 or nullptr. Typically you add the formal parameter name, which ought to be meaningful. For example, it’s not clear what the parameter means in this call:

【译】在函数调用中,描述常量实参的重要性时。这对bool参数,或者传0以及传nullptr的场景最有用。通常添加形参名字的注释应该就很有意义了。例如,在下面这个调用中,参数的含义并不清晰:

1
Object.emitName(nullptr);

An in-line C-style comment makes the intent obvious:

【译】内嵌的C风格注释让这个意图变得很明晰。

1
Object.emitName(/*Prefix=*/nullptr);

【注】通常会使用有意义的常量名、更有意义的函数名、或重构成员函数的设计等方式来让意图变得明晰。这类注释往往是不得已而为之,大部分场景下,恰恰证明此函数设计的缺陷,当重构之,而非注释之。

Commenting out large blocks of code is discouraged, but if you really have to do this (for documentation purposes or as a suggestion for debug printing), use #if 0 and #endif. These nest properly and are better behaved in general than C style comments.

【译】不鼓励注释掉大块代码,但如果真的必须得这样做(出于文档的目的或者作为调试打印的建议),使用 #if 0#endif。这些可以保证嵌套的正确性,而且通常比C风格的注释表现更好。

Doxygen Use in Documentation Comments(文档注释中Doxygen的应用)

Use the \file command to turn the standard file header into a file-level comment.

【译】使用\file命令将标准文件头注释转换为文件级注释。

Include descriptive paragraphs for all public interfaces (public classes, member and non-member functions). Don’t just restate the information that can be inferred from the API name. The first sentence (or a paragraph beginning with \brief) is used as an abstract. Try to use a single sentence as the \brief adds visual clutter. Put detailed discussion into separate paragraphs.

【译】所有公共接口(公共类、成员与非成员函数)都需要包含一个描述性段落,不要只赘述那些可从API名称推断出来的信息。第一句(以\brief开头的段落)作为摘要,若\brief会产生混淆,试着使用单句。细节描述放到其他段落。

To refer to parameter names inside a paragraph, use the \p name command. Don’t use the \arg name command since it starts a new paragraph that contains documentation for the parameter.

【译】若要引用段落中的参数名称,使用\p name命令。不要使用\arg name命令,因为它会开始一个包含参数注释的新段。

Wrap non-inline code examples in \code ... \endcode.

【译】把非内联代码示例放在\code ... \endcode中。

To document a function parameter, start a new paragraph with the \param name command. If the parameter is used as an out or an in/out parameter, use the \param [out] name or \param [in,out] name command, respectively.

【译】若要注释函数参数,使用\param name新起一段。如果是一个输出或输入/输出参数,分别使用\param [out] name\param [in,out] name命令。

To describe function return value, start a new paragraph with the \returns command.

【译】若要注释函数返回值,使用\returns新起一段。

A minimal documentation comment:

【译】一个极简的注释示例:

1
2
/// Sets the xyzzy property to \p Baz.
void setXyzzy(bool Baz);

A documentation comment that uses all Doxygen features in a preferred way:

【译】一个较好的使用了所有Doxygen特性的注释示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/// Does foo and bar.
///
/// Does not do foo the usual way if \p Baz is true.
///
/// Typical usage:
/// \code
///   fooBar(false, "quux", Res);
/// \endcode
///
/// \param Quux kind of foo to do.
/// \param [out] Result filled with bar sequence on foo success.
///
/// \returns true on success.
bool fooBar(bool Baz, StringRef Quux, std::vector<int> &Result);

Don’t duplicate the documentation comment in the header file and in the implementation file. Put the documentation comments for public APIs into the header file. Documentation comments for private APIs can go to the implementation file. In any case, implementation files can include additional comments (not necessarily in Doxygen markup) to explain implementation details as needed.

【译】不要在头文件和实现文件中重复注释,将公共API的注释放在头文件中,将私有API的注释放在实现文件中。任何时候,实现文件都可以包含其他注释(不一定在Doxygen标记中),以便按需解释实现细节。

Don’t duplicate function or class name at the beginning of the comment. For humans it is obvious which function or class is being documented; automatic documentation processing tools are smart enough to bind the comment to the correct declaration.

【译】不要在注释的开头重复函数名或类名。因为哪个函数或类被注释是显而易见的,而且自动注释处理工具可以很智能的将注释关联到正确的声明上。

Wrong:

【译】错误示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// In Something.h:

/// Something - An abstraction for some complicated thing.
class Something {
public:
  /// fooBar - Does foo and bar.
  void fooBar();
};

// In Something.cpp:

/// fooBar - Does foo and bar.
void Something::fooBar() { ... }

Correct:

【译】正确做法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// In Something.h:

/// An abstraction for some complicated thing.
class Something {
public:
  /// Does foo and bar.
  void fooBar();
};

// In Something.cpp:

// Builds a B-tree in order to do foo.  See paper by...
void Something::fooBar() { ... }

It is not required to use additional Doxygen features, but sometimes it might be a good idea to do so.

【译】一般来说,我们不需要再使用其他Doxygen特性,但有些情况下,使用其他特性会比较好。

Consider:

【译】思考下:

  • adding comments to any narrow namespace containing a collection of related functions or types;

【译】给用于包含关联函数或类型集的狭义命名空间添加注释;

  • using top-level groups to organize a collection of related functions at namespace scope where the grouping is smaller than the namespace;

【译】使用顶层分组在命名空间范围内组织关联函数集,其中分组范围小于命名空间;

  • using member groups and additional comments attached to member groups to organize within a class.

【译】使用成员分组,并附加额外的注释来管理类内的成员分组。

For example:

【译】例如:

1
2
3
4
5
6
7
8
class Something {
  /// \name Functions that do Foo.
  /// @{
  void fooBar();
  void fooBaz();
  /// @}
  ...
};

#include Style(#include风格)

Immediately after the header file comment (and include guards if working on a header file), the minimal list of #includes required by the file should be listed. We prefer these #includes to be listed in this order:

【译】在文件头注释之后(如果是头文件,在头文件保护符范围内),列出#include的最小集。我们希望按以下的顺序列出#include

【注】"#include的最小集",存在一定的歧义:不要引入多余的头文件,且头文件隐式包含的其他头文件不用再引入;不要引入多余的头文件,头文件隐式包含的其他头文件若因其他用途被需要,则仍需显示引入,避免隐式依赖。个人更倾向于后者。

  1. Main Module Header【译】主模块头文件
  2. Local/Private Headers【译】临近/私有的头文件
  3. LLVM project/subproject headers (clang/..., lldb/..., llvm/..., etc)【译】LLVM项目/子项目的头文件
  4. System #includes【译】系统#include

and each category should be sorted lexicographically by the full path.

【译】并且每个分类需要按照全路径的的字典顺序进行排序。

【注】主模块头文件排第一,主要为保证头文件自包含。之后的文件分类排序在不同的公司标准中,大概可以分为这两类:由远及近、由通用到特殊、影响域逐渐缩小;由近及远、由特殊到通用、影响域逐渐增大。LLVM编码规范采用第二种。

The Main Module Header file applies to .cpp files which implement an interface defined by a .h file. This #includeshould always be included first regardless of where it lives on the file system. By including a header file first in the .cpp files that implement the interfaces, we ensure that the header does not have any hidden dependencies which are not explicitly #included in the header, but should be. It is also a form of documentation in the .cpp file to indicate where the interfaces it implements are defined.

【译】若.cpp文件实现了某.h文件定义的接口,此.h文件为该.cpp文件的主模块头文件。无论该文件位于何处,它都始终应该被第一个#include,这样就能确保头没有没有任何隐式依赖。同时,这也是一种文档形式,用于表明该.cpp文件实现的接口定义在哪里。

LLVM project and subproject headers should be grouped from most specific to least specific, for the same reasons described above. For example, LLDB depends on both clang and LLVM, and clang depends on LLVM. So an LLDB source file should include lldb headers first, followed by clang headers, followed by llvm headers, to reduce the possibility (for example) of an LLDB header accidentally picking up a missing include due to the previous inclusion of that header in the main source file or some earlier header file. clang should similarly include its own headers before including llvm headers. This rule applies to all LLVM subprojects.

【译】出于和上述相同的原因,LLVM项目和子项目的头文件也应该按照从特殊到通用的原则分组。举例来说,LLDB依赖clang和LLVM,同时clang也依赖于LLVM。那么,一个LLDB源文件应先包含lldb头文件,其次是clang头文件,接着是llvm头文件。这样可降低因在此之前主源文件或头文件引入了头文件,导致LLDB头文件缺少包含的可能性。同理,clang应该先包含自己的头文件,然后才是llvm头文件。此规则适用于所有LLVM子项目。

Source Code Width(源码行宽)

Write your code to fit within 80 columns of text. This helps those of us who like to print out code and look at your code in an xterm without resizing it.

【译】代码行宽不超过80个字符,这有助于喜欢打印代码或在xterm上阅读代码的人无需重新调整行宽大小。

The longer answer is that there must be some limit to the width of the code in order to reasonably allow developers to have multiple files side-by-side in windows on a modest display. If you are going to pick a width limit, it is somewhat arbitrary but you might as well pick something standard. Going with 90 columns (for example) instead of 80 columns wouldn’t add any significant value and would be detrimental to printing out code. Also many other projects have standardized on 80 columns, so some people have already configured their editors for it (vs something else, like 90 columns).

【译】更长远的解释,为适度的允许开发者在适中的显示器窗口上将多个文件并排放置,代码行宽必须要进行一些限制。如果你准备选择一个行宽限制,这某种程度上有些随意,但我们还是有些标准可供选择的。例如使用90列而非80列,这并不会添加什么重要信息,反而不利于打印代码。此外,许多其他项目已经使用80列作为标准,有些人已经为此配置好了编辑器(和其他列宽如90列相比)

This is one of many contentious issues in coding standards, but it is not up for debate.

【译】这是编码规范中有争议的问题之一,但还没上升到需要辩论的地步。

Whitespace(空格)

In all cases, prefer spaces to tabs in source files. People have different preferred indentation levels, and different styles of indentation that they like; this is fine. What isn’t fine is that different editors/viewers expand tabs out to different tab stops. This can cause your code to look completely unreadable, and it is not worth dealing with.

【译】在任何时候,优先使用空格而不是tab。不同的人倾向不同的缩进级别和缩进风格,这是合理的。但不同的编辑器或阅读器将tab展开为不同的宽度展示,这会导致你的代码看起来完全不可读,且没有继续理解的必要。

As always, follow the Golden Rule above: follow the style of existing code if you are modifying and extending it. If you like four spaces of indentation, DO NOT do that in the middle of a chunk of code with two spaces of indentation. Also, do not reindent a whole source file: it makes for incredible diffs that are absolutely worthless.

【译】如上文所说,遵循以下黄金法则:如果修改或扩展存量代码,遵循现有代码风格。无论你多么喜欢4空格缩进,请勿在两空格缩进的代码块中使用。此外,不要调整整个源文件的缩进风格:这会产生毫无意义但令人费解的差异。

Do not commit changes that include trailing whitespace. If you find trailing whitespace in a file, do not remove it unless you’re otherwise changing that line of code. Some common editors will automatically remove trailing whitespace when saving a file which causes unrelated changes to appear in diffs and commits.

【译】请勿提交行尾带有空格的变更。如果文件中有行尾带空格的代码,除非正在修改那行,否则不要修改(如上文所述:格式化、重构和功能开发应以不同的patch提交)。一些常见的编辑器会在保存文件时自动删除行尾的空格,这会导致无关的变更出现在差异和提交(git commit的变更比较)中。

Indent Code Consistently(代码缩进一致)

Okay, in your first year of programming you were told that indentation is important. If you didn’t believe and internalize this then, now is the time. Just do it. With the introduction of C++11, there are some new formatting challenges that merit some suggestions to help have consistent, maintainable, and tool-friendly formatting and indentation.

【译】在你接触程序开发的第一年,会被告知缩进很重要。如果你不信也不想接受这点,现在是时候了,做吧。随着C++11的推出,一些新的格式挑战,需要一些建议来帮助实现一致的、可维护的、工具有好的格式和缩进。

Format Lambdas Like Blocks Of Code(像代码块一样格式化lambda表达式)

When formatting a multi-line lambda, format it like a block of code, that’s what it is. If there is only one multi-line lambda in a statement, and there are no expressions lexically after it in the statement, drop the indent to the standard two space indent for a block of code, as if it were an if-block opened by the preceding part of the statement:

【译】使用代码块的风格来格式化多行lambda表达式。如果一条语句中仅包含一个多行lambda,且之后没有词法表达式,则使用标准的两空格缩进,就像展开的if块。

1
2
3
4
5
6
7
std::sort(foo.begin(), foo.end(), [&](Foo a, Foo b) -> bool {
  if (a.blah < b.blah)
    return true;
  if (a.baz < b.baz)
    return true;
  return a.bam < b.bam;
});

To take best advantage of this formatting, if you are designing an API which accepts a continuation or single callable argument (be it a functor, or a std::function), it should be the last argument if at all possible.

【译】如果你正在设计一个接受连续或单个可调用的参数(仿函数或std::function),为了充分利用这种格式的优势,尽可能把它作为最后一个参数。

If there are multiple multi-line lambdas in a statement, or there is anything interesting after the lambda in the statement, indent the block two spaces from the indent of the []:

【译】如果一条语句中包含多个多行lambda,或lambda后还有其他内容,相对[]的缩进,lambda块内容再缩进两个空格。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
dyn_switch(V->stripPointerCasts(),
           [] (PHINode *PN) {
             // process phis...
           },
           [] (SelectInst *SI) {
             // process selects...
           },
           [] (LoadInst *LI) {
             // process loads...
           },
           [] (AllocaInst *AI) {
             // process allocas...
           });
Braced Initializer Lists(大括号初始化列表)

With C++11, there are significantly more uses of braced lists to perform initialization. These allow you to easily construct aggregate temporaries in expressions among other niceness. They now have a natural way of ending up nested within each other and within function calls in order to build up aggregates (such as option structs) from local variables. To make matters worse, we also have many more uses of braces in an expression context that are not performing initialization.

【译】在C++11中,使用大括号进行列表初始化的情况多了很多。它使你很容易在表达式中结构化组合临时值,当然还有其他好处。它们现在可以很自然的在函数调用中相互嵌套,以此通过局部变量构建组合结构(如选项结构)。嵌套过多并不好,而更糟糕的是,一个表达式的上下文中同样会有更多执行初始化的大括号。

The historically common formatting of braced initialization of aggregate variables does not mix cleanly with deep nesting, general expression contexts, function arguments, and lambdas. We suggest new code use a simple rule for formatting braced initialization lists: act as-if the braces were parentheses in a function call. The formatting rules exactly match those already well understood for formatting nested function calls.

【译】基于历史经验,聚合变量的大括号初始化通常不会与深层嵌套、一般的表达式、函数参数以及lambda混在一起。建议新代码使用简单的规则来格式化大括号初始化列表:就像函数调用中大括号的行为。其格式化规范和嵌套函数调用的格式化规范完全一致。

Examples:

【译】示例:

1
2
3
4
5
6
foo({a, b, c}, {1, 2, 3});

llvm::Constant *Mask[] = {
    llvm::ConstantInt::get(llvm::Type::getInt32Ty(getLLVMContext()), 0),
    llvm::ConstantInt::get(llvm::Type::getInt32Ty(getLLVMContext()), 1),
    llvm::ConstantInt::get(llvm::Type::getInt32Ty(getLLVMContext()), 2)};

This formatting scheme also makes it particularly easy to get predictable, consistent, and automatic formatting with tools like Clang Format.

【译】这样的格式化方案,可以很方便的利用自动格式化工具(如Clang Format)得到可以预计的、稳定的格式。

Language and Compiler Issues(语言和编译器)

Treat Compiler Warnings Like Errors(将编译器告警视为错误)

If your code has compiler warnings in it, something is wrong — you aren’t casting values correctly, you have “questionable” constructs in your code, or you are doing something legitimately wrong. Compiler warnings can cover up legitimate errors in output and make dealing with a translation unit difficult.

【译】如果代码存在编译告警,说明有问题——没有正确的类型转换,有可疑的构造,或者正在做错误的操作。编译告警会掩盖输出中的错误,并让编译器翻译单元处理起来更困难。

It is not possible to prevent all warnings from all compilers, nor is it desirable. Instead, pick a standard compiler (like gcc) that provides a good thorough set of warnings, and stick to it. At least in the case of gcc, it is possible to work around any spurious errors by changing the syntax of the code slightly. For example, a warning that annoys me occurs when I write code like this:

【译】处理所有编译器的所有告警是不太现实的,也不可取。相反,挑一个有一套标准优质告警的编译器(如gcc)作为标准,并坚持下去。至少像gcc这样的编译器,可以通过稍微修改代码语法来解决任何误报。例如,下面这种写法会报出一个恼人的告警:

1
2
3
if (V = getValue()) {
  ...
}

gcc will warn me that I probably want to use the == operator, and that I probably mistyped it. In most cases, I haven’t, and I really don’t want the spurious errors. To fix this particular problem, I rewrite the code like this:

【译】gcc将提醒这里可能期望使用==,但手误写成了=。大多数情况下,我并没有写错,我不需要这种误报。为了修复这个特殊问题,重构代码如下:

1
2
3
if ((V = getValue())) {
  ...
}

which shuts gcc up. Any gcc warning that annoys you can be fixed by massaging the code appropriately.

【译】这规避了告警,任何gcc告警都可以通过适当改变代码来修复。

【注】warning中确实存在一些有争议的项,通常编译器有关闭某类告警的开关。但此处举例不恰当,最大的问题在if ((V = getValue()))中,V = getValue()先执行赋值动作,然后V做布尔类型转换,前者破坏条件表达式中判断表达式只做判断的单一职责,后者采用默认转换语义不清。此写法不值得推荐,如果想限制V的作用域,请抽函数或使用{}控制,推荐写法如下:

1
2
3
4
auto V = getValue();
if (V != 0) {
  ...
}

Write Portable Code(写可移植的代码)

In almost all cases, it is possible and within reason to write completely portable code. If there are cases where it isn’t possible to write portable code, isolate it behind a well defined (and well documented) interface.

【译】几乎在所有情况下,写完全可移植的代码是可行的,而且是合理的。如果某些场景不太可能写出可移植的代码,将其隔离在一个定义完善的(注释完善)接口内。

【注】使用接口隔离是开发过程常见的手段,在一个紧迫交付的需求或知识不足以写出一定水准的代码时,往往会将这段设计粗糙、修改频繁或理解不足的代码剥离出来实现,这样可以在后续继续演进、以及当编码水平达到一定程度后重构。设计规则:可以容忍脏乱差的代码存在一定时间,但不能污染已有代码,或影响后续调用者。

In practice, this means that you shouldn’t assume much about the host compiler (and Visual Studio tends to be the lowest common denominator). If advanced features are used, they should only be an implementation detail of a library which has a simple exposed API, and preferably be buried in libSystem.

【译】这意味着在写代码的时候,不应该对主流编译器做过多假设(Visual Studio做的最差)。如果使用了高级功能,那他们的实现细节应该隐藏在库中,并只暴露简单的接口。

【注】接口与实现分离,接口抽象往往具有完整局部业务结构,并不是任意函数的堆砌,这是常犯的问题。接口中暴露必要的信息,而不是越多越好,尽量避免二次接口依赖,衡量标准:在头文件自包含的情况下,引用非公共的头文件应该合理的尽量少。

Do not use RTTI or Exceptions(禁用运行时类型识别和异常)

In an effort to reduce code and executable size, LLVM does not use RTTI (e.g. dynamic_cast<>;) or exceptions. These two language features violate the general C++ principle of “you only pay for what you use”, causing executable bloat even if exceptions are never used in the code base, or if RTTI is never used for a class. Because of this, we turn them off globally in the code.

【译】为了代码和可执行文件大小,LLVM不使用RTTI(如dynamic_cast<>;)和异常。这两种语言特性违反了“你只用为你所用付出代价”的通用C++原则,它们会导致代码膨胀,而且即使异常未被使用,或者RTTI未用于某个类。因此,我们在全局代码中禁用了它们。

That said, LLVM does make extensive use of a hand-rolled form of RTTI that use templates like isa<>, cast<>, and dyn_cast<>. This form of RTTI is opt-in and can be added to any class. It is also substantially more efficient than dynamic_cast<>.

【译】也就是说,LLVM广泛使用了手动形式的RTTI,如 isa<>, cast<>, and dyn_cast<>等模板。这类形式的RTTI是可用的,并可添加到任何类中。而且它也比dynamic_cast<>更高效。

Do not use Static Constructors(禁用静态构造)

Static constructors and destructors (e.g. global variables whose types have a constructor or destructor) should not be added to the code base, and should be removed wherever possible. Besides well known problems where the order of initialization is undefined between globals in different source files, the entire concept of static constructors is at odds with the common use case of LLVM as a library linked into a larger application.

【译】静态构造和析构(如,类型中包含构造函数和析构函数的全局变量)不应添加到代码库中,并应尽可能删除。除了在不同源文件中全局变量之间的初始化顺序不确定这个众所周知的问题外,静态构造的整个概念都和LLVM作为库链接到大型软件这样的常见场景相悖。

【注】这里Static Constructor推断应理解为静态构造,构建并初始化全局对象或类内的静态对象等,而非静态构造函数的含义。

Consider the use of LLVM as a JIT linked into another application (perhaps for OpenGL, custom languages, shaders in movies, etc). Due to the design of static constructors, they must be executed at startup time of the entire application, regardless of whether or how LLVM is used in that larger application. There are two problems with this:

【译】考虑LLVM作为JIT链接到另外一个软件的情况(可能用于OpenGL、定制语言、OpenShadingLanguage等)。由于设计了静态构造,无论LLVM在大型应用中是否被用到,它们必须在整个应用程序启动时执行。这就存在两个问题:

  • The time to run the static constructors impacts startup time of applications — a critical time for GUI apps, among others.

【译】运行静态构造影响应用的启动时间——这是GUI应用的主要时间,等等。

  • The static constructors cause the app to pull many extra pages of memory off the disk: both the code for the constructor in each .o file and the small amount of data that gets touched. In addition, touched/dirty pages put more pressure on the VM system on low-memory machines.

【译】静态构造让应用从磁盘中提取许多额外的内存页:包括每个.o文件中的构造代码,以及少量相关数据。另外,在内存紧缺的机器上,涉及的额外内存页给VM系统增加了更多的压力。

We would really like for there to be zero cost for linking in an additional LLVM target or other library into an application, but static constructors violate this goal.

【译】我们希望应用程序链接一个LLVM项目或其他库时零消耗,但静态构造违背了这个目标。

That said, LLVM unfortunately does contain static constructors. It would be a great project for someone to purge all static constructors from LLVM, and then enable the -Wglobal-constructors warning flag (when building with Clang) to ensure we do not regress in the future.

【译】但不幸的是,LLVM着实包含了静态构造。如果有人可以清除LLVM中所有的静态构造,并启用-Wglobal-constructors告警选项(当用Clang编译时)来保证这个特性不会退化,那将是个伟大的工程。

Use of class and struct Keywords(classstruct关键字的使用)

In C++, the class and struct keywords can be used almost interchangeably. The only difference is when they are used to declare a class: class makes all members private by default while struct makes all members public by default.

【译】在C++中,classstruct关键字几乎可以互换。唯一的区别是当他们用于声明类时:class默认所有成员私有,而struct默认所有成员公有。

Unfortunately, not all compilers follow the rules and some will generate different symbols based on whether class or struct was used to declare the symbol (e.g., MSVC). This can lead to problems at link time.

【译】然而不幸的是,并不是所有编译器都遵循这个规则,有些编译器(如MSVC)会将classstruct关键字声明的符号生成为不同的符号。这可能会导致链接时问题。

  • All declarations and definitions of a given class or struct must use the same keyword. For example:

【译】给定classstruct的所有声明和定义必须使用相同的关键字,例如:

1
2
3
4
class Foo;

// Breaks mangling in MSVC.
struct Foo { int Data; };
  • As a rule of thumb, struct should be kept to structures where all members are declared public.

【译】根据经验,struct关键字仅限于使用在所有成员都声明为公有的情况下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Foo feels like a class... this is strange.
struct Foo {
private:
  int Data;
public:
  Foo() : Data(0) { }
  int getData() const { return Data; }
  void setData(int D) { Data = D; }
};

// Bar isn't POD, but it does look like a struct.
struct Bar {
  int Data;
  Bar() : Data(0) { }
};

Do not use Braced Initializer Lists to Call a Constructor(禁用大括号初始化列表来调用构造函数)

In C++11 there is a “generalized initialization syntax” which allows calling constructors using braced initializer lists. Do not use these to call constructors with any interesting logic or if you care that you’re calling some particular constructor. Those should look like function calls using parentheses rather than like aggregate initialization. Similarly, if you need to explicitly name the type and call its constructor to create a temporary, don’t use a braced initializer list. Instead, use a braced initializer list (without any type for temporaries) when doing aggregate initialization or something notionally equivalent. Examples:

【译】在C++11中,有个"通用初始化语法":使用大括号初始化列表调用构造函数。不要使用这来调用具有任何特殊逻辑的构造函数,以及某个特定的构造函数。这看起来更像使用括号的函数而不是聚合初始化。同样的,如果需要显示指定类型,并使用其构造函数来创建一个临时变量,不要使用大括号初始化列表。相反,不显示指定类型时,在聚合初始化或类似的情况下,使用大括号初始化列表(无任何类型的临时变量)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Foo {
public:
  // Construct a Foo by reading data from the disk in the whizbang format, ...
  Foo(std::string filename);

  // Construct a Foo by looking up the Nth element of some global data ...
  Foo(int N);

  // ...
};

// The Foo constructor call is very deliberate, no braces.
std::fill(foo.begin(), foo.end(), Foo("name"));

// The pair is just being constructed like an aggregate, use braces.
bar_map.insert({my_key, my_value});

If you use a braced initializer list when initializing a variable, use an equals before the open curly brace:

【译】当使用大括号初始化列表初始化变量时,在大括号前使用等号。

1
int data[] = {0, 1, 2, 3};

Use auto Type Deduction to Make Code More Readable(使用auto自动类型推导提升代码可读性)

Some are advocating a policy of “almost always auto” in C++11, however LLVM uses a more moderate stance. Use auto if and only if it makes the code more readable or easier to maintain. Don’t “almost always” use auto, but do useauto with initializers like cast<Foo>(...) or other places where the type is already obvious from the context. Another time when auto works well for these purposes is when the type would have been abstracted away anyways, often behind a container’s typedef such as std::vector<T>::iterator.

【译】有些人在C++11中提倡"全部总是auto",但llvm采取更加适度的态度。当且仅当auto能使代码可读性更强、可维护性更好时才使用它。不要"全部总是"使用auto,但像在cast<Foo>(…)这样的初始化或类型在上下文显而易见的场景下,确实应该使用auto。另外,在类型反正会被抽象化的情况下,通常是某个容器内的typedefstd::vector<T>::iteratorauto的效果也很好。

【注】auto是一种类似于强类型语言python变量定义的语义,形式上又如弱类型语言javascript中的var。这种方式的体验感很是酸爽,一方面代码表现的很简洁,不啰嗦;另外一方面,即使命名技巧很强,代码分析时也容易卡壳,不得不分析上下文。auto的使用,更推荐是仅在很小的语块(3句左右)中使用的、类型在语块中明确可见的、机械性重复给重命名带来额外工作量的类型使用auto替代。译文中的would have been abstracted表达的是其概念高度抽象化,无论其实际类型是什么都不影响这类概念的使用方式。举例来说:

1
2
3
4
5
6
7
...
std::vector<T>::iterator It = std::find(Vec.begin(), Vec.end(), ElemToFind);
if (It == Vec.end())
  return;
T& Elem = *It
...
return Elem.xxx + Elem.yyy;

std::vector<T>::iterator It推荐使用autoIt承载的是迭代器的概念,两处使用if (It == Vec.end())是与T类型无关的属于迭代器抽象模型的逻辑算法,T& Elem = *It在简短语块中清晰可见。T& Elem = *It不推荐使用auto,因为下文有若干特定业务的计算逻辑,此处需要保持类型描述清晰,否则需要反向推导直到发现std::find中的Vec的类型。由于对auto使用的判断具有一定的主观性,且依赖于历史开发维护经验,所以若当是否用auto的选择不能快速直观的给出结论时,不要使用auto,少量的啰嗦总强于语意不清。

Beware unnecessary copies with auto(警惕不必要的auto拷贝)

The convenience of auto makes it easy to forget that its default behavior is a copy. Particularly in range-based forloops, careless copies are expensive.

【译】auto的易用性使仁恒很容易忘记它的默认行为是拷贝。尤其是在基于范围的for循环中,这种粗心导致的拷贝代价很大。

As a rule of thumb, use auto & unless you need to copy the result, and use auto * when copying pointers.

【译】根据经验,除非拷贝结果,否则使用auto &,并使用auto *拷贝指针。

【注】引用从本质上来说并不是一种对象(虽然编译器可能类似指针实现),没有引用的引用,没有指向引用的指针,也没有引用数组,它是一种别名关系,所以静态类型推导无法识别引用类型,auto使用需要显示区分引用和拷贝便是这个原因。指针是一种数据类型,此处显示拷贝指针所要防范的场景未知,或许仅仅为了和引用保持一致关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Typically there's no reason to copy.
for (const auto &Val : Container) { observe(Val); }
for (auto &Val : Container) { Val.change(); }

// Remove the reference if you really want a new copy.
for (auto Val : Container) { Val.change(); saveSomewhere(Val); }

// Copy pointers, but make it clear that they're pointers.
for (const auto *Ptr : Container) { observe(*Ptr); }
for (auto *Ptr : Container) { Ptr->change(); }

Beware of non-determinism due to ordering of pointers(警惕指针排序的不确定性)

In general, there is no relative ordering among pointers. As a result, when unordered containers like sets and maps are used with pointer keys the iteration order is undefined. Hence, iterating such containers may result in non-deterministic code generation. While the generated code might not necessarily be “wrong code”, this non-determinism might result in unexpected runtime crashes or simply hard to reproduce bugs on the customer side making it harder to debug and fix.

【译】通常,指针之间不存在相对顺序。因此,当指针作为集合和映射这样的无序容器的主键时,迭代顺序是未定义的。而且迭代这样的容器可能会导致不确定代码的生成。虽然生成的代码不一定是“错误的代码”,但这种非确定性可能会导致意外的运行时崩溃、或客户发现的错误难以重现,从而使调试和修复变得更加困难。

As a rule of thumb, in case an ordered result is expected, remember to sort an unordered container before iteration. Or use ordered containers like vector/MapVector/SetVector if you want to iterate pointer keys.

【译】根据经验,如果需要有序结果,记住在迭代之前对无序容器进行排序。 或者,如果要迭代指针键,使用vector / MapVector / SetVector等有序容器。

【注】虽然LLVM中存在使用指针作为排序关键字的场景,但无论其对象的排序是否稳定,这种用法本身就违背了诸多原则。例如,赋予指针除标记地址外的另外的职责,违背SOLID中单一职责的设计原则,而此处的迭代主键本应该由对象中的部分或全部成员,或hask、UUID等算法构建,偷懒的嫌疑居多。所以,指针除用于记录对象地址以及传递外,不应该参与其他任何算法与逻辑。

Beware of non-deterministic sorting order of equal elements(警惕相等对象排序的不确定性)

std::sort uses a non-stable sorting algorithm in which the order of equal elements is not guaranteed to be preserved. Thus using std::sort for a container having equal elements may result in non-determinstic behavior. To uncover such instances of non-determinism, LLVM has introduced a new llvm::sort wrapper function. For an EXPENSIVE_CHECKS build this will randomly shuffle the container before sorting. As a rule of thumb, always make sure to use llvm::sort instead of std::sort.

【译】std :: sort使用了非稳定排序算法,它不保证相等元素的顺序。 因此,对具有相等成员的容器使用std :: sort可能导致不确定性行为。 为了解决这种不确定情况,LLVM引入了一个新的llvm :: sort函数封装。 对于EXPENSIVE_CHECKS构建,这将在排序之前随机地移动容器。 根据经验,始终确保使用llvm :: sort而不是std :: sort。

Style Issues(格式)

The High-Level Issues(高级主题)

Self-contained Headers(自包含的头文件)

Header files should be self-contained (compile on their own) and end in .h. Non-header files that are meant for inclusion should end in .inc and be used sparingly.

【译】头文件应该自包含(独立编译),并以.h作为文件扩展名。非头文件但也用于被包含的,应该谨慎使用,并以.inc作为文件扩展名。

All header files should be self-contained. Users and refactoring tools should not have to adhere to special conditions to include the header. Specifically, a header should have header guards and include all other headers it needs.

【译】所有头文件都应该自包含。使用者和重构工具不应该因为某些特殊的状况来包含头文件。具体而言,头文件应该包含标题头保护以及它所必须的其他头文件。

There are rare cases where a file designed to be included is not self-contained. These are typically intended to be included at unusual locations, such as the middle of another file. They might not use header guards, and might not include their prerequisites. Name such files with the .inc extension. Use sparingly, and prefer self-contained headers when possible.

【译】有极少的情况,一个文件会被设计成被包含但却不自包含。这些通常被包含在特殊的位置,例如另一个文件的中间位置。 他们可能不使用标题保护,可能不包括他们的依赖。 使用.inc扩展名命名此类文件。 谨慎使用,并尽可能选择自包含的头文件。

In general, a header should be implemented by one or more .cpp files. Each of these .cpp files should include the header that defines their interface first. This ensures that all of the dependences of the header have been properly added to the header itself, and are not implicit. System headers should be included after user headers for a translation unit.

【译】通常,头文件实现在一个或多个.cpp文件中。每个头文件都应该包含此头文件,并首先定义相关接口。这可以确保头文件的所有依赖都已正确的被添加到开头,并且不是隐式的。在一个编译单元中,系统头文件应该包含在用户头文件之后。

Library Layering(库分层)

A directory of header files (for example include/llvm/Foo) defines a library (Foo). Dependencies between libraries are defined by the LLVMBuild.txt file in their implementation (lib/Foo). One library (both its headers and implementation) should only use things from the libraries listed in its dependencies.

【译】一个头文件目录(如include/llvm/Foo)定义了一个库(Foo)。库之间的依赖关系在实现(lib / Foo)目录下的LLVMBuild.txt文件中定义。 一个库(头和实现)应该只使用其依赖库中的东西。

Some of this constraint can be enforced by classic Unix linkers (Mac & Windows linkers, as well as lld, do not enforce this constraint). A Unix linker searches left to right through the libraries specified on its command line and never revisits a library. In this way, no circular dependencies between libraries can exist.

【译】一些约束可以由经典的Unix链接器强制执行(Mac和Windows链接器,以及lld,不强制执行此约束)。 Unix链接器从左到右搜索其命令行中指定的库,不会再重新访问一个库。 这样,库之间就不可能循环依赖了。

This doesn’t fully enforce all inter-library dependencies, and importantly doesn’t enforce header file circular dependencies created by inline functions. A good way to answer the “is this layered correctly” would be to consider whether a Unix linker would succeed at linking the program if all inline functions were defined out-of-line. (& for all valid orderings of dependencies - since linking resolution is linear, it’s possible that some implicit dependencies can sneak through: A depends on B and C, so valid orderings are “C B A” or “B C A”, in both cases the explicit dependencies come before their use. But in the first case, B could still link successfully if it implicitly depended on C, or the opposite in the second case)

【译】这并不会强制所有库间的依赖关系,并且重要的是,不会强制执行内联函数引起的头文件循环依赖关系。 回答“正确分层”的一个好方法是,考虑如果所有内联函数都定义成外联,Unix链接器是否能成功链接程序。 (对于依赖关系的所有有效排序 - 因为链接决议是线性的,所以有可能即使存在某些隐式依赖关系也可以链接通过:A依赖于B和C,因此有效排序是“CBA”或“BCA”,这两种情况下,显式依赖关系都在它们使用之前确定。但在第一种情况下,如果B隐式依赖于C的话,B仍然可以链接成功,或者反过来在第二种情况下。)

#include as Little as Possible(头文件最小依赖)

#include hurts compile time performance. Don’t do it unless you have to, especially in header files.

【译】#include会影响编译时间方面的性能,除非必须,否则不要新增#include,尤其是在头文件中。

But wait! Sometimes you need to have the definition of a class to use it, or to inherit from it. In these cases go ahead and #include that header file. Be aware however that there are many cases where you don’t need to have the full definition of a class. If you are using a pointer or reference to a class, you don’t need the header file. If you are simply returning a class instance from a prototyped function or method, you don’t need it. In fact, for most cases, you simply don’t need the definition of a class. And not #includeing speeds up compilation.

【译】可是等等!有时需要类的定义来使用它,或者继承它,在这些情况下,#include对应的头文件。但需要注意,有很多场景并不需要类的完整定义。如果正在使用一个类的指针或引用,则不需要包含对应的头文件。如果简单的从一个原型方法或函数返回一个类实例,也不需要。实际上,大多数场景下,并不需要一个类的定义,如果不#include,那将提升编译的速度。

It is easy to try to go too overboard on this recommendation, however. You must include all of the header files that you are using — you can include them either directly or indirectly through another header file. To make sure that you don’t accidentally forget to include a header file in your module header, make sure to include your module header first in the implementation file (as mentioned above). This way there won’t be any hidden dependencies that you’ll find out about later.

【译】然而,很容易过度解读这个建议。必须包含正在使用的所有头文件——可以直接包含,也可以通过其他头文件间接包含。为了确保不会在模块头文件中遗漏一个头文件,保证在实现文件中首先包含模块头文件(前面已提到),这样任何隐藏的依赖关系都不会存在。

Keep “Internal” Headers Private(保持"内部"头文件私有)

Many modules have a complex implementation that causes them to use more than one implementation (.cpp) file. It is often tempting to put the internal communication interface (helper classes, extra functions, etc) in the public module header file. Don’t do this!

【译】有些模块实现复杂,以至于需要多个实现文件(.cpp)。这经常会诱导将内部接口(辅助类、额外的方法等等)放到公共的模块头文件中。但不要这么做。

If you really need to do something like this, put a private header file in the same directory as the source files, and include it locally. This ensures that your private interface remains private and undisturbed by outsiders.

【译】如果确实需要这样做,在源文件同目录下加一个私有文头文件,并在当前目录下包含它。这确保了私有接口依然私有,而且不会被外部干扰。

Note(注意)

It’s okay to put extra implementation methods in a public class itself. Just make them private (or protected) and all is well.

【译】将额外的实现函数放在它的公有类中是可以的,只要设置成私有(或集成)的就成。

Use Early Exits and continue to Simplify Code(提前使用退出和continue来简化代码)

When reading code, keep in mind how much state and how many previous decisions have to be remembered by the reader to understand a block of code. Aim to reduce indentation where possible when it doesn’t make it more difficult to understand the code. One great way to do this is by making use of early exits and the continue keyword in long loops. As an example of using an early exit from a function, consider this “bad” code:

【译】阅读代码时,记录下读者必须记住多少个状态、多少个前置判断才能理解一段代码。目的是,只要代码不会变的更难理解,尽可能去减少缩进。达成这个目的,有个很好的方法是提前退出,以及长循环中使用continue关键字。举一个函数提前退出的例子,思考下面的"坏"代码:

1
2
3
4
5
6
7
8
Value *doSomething(Instruction *I) {
  if (!I->isTerminator() &&
      I->hasOneUse() && doOtherThing(I)) {
    ... some long code ....
  }

  return 0;
}

This code has several problems if the body of the 'if' is large. When you’re looking at the top of the function, it isn’t immediately clear that this only does interesting things with non-terminator instructions, and only applies to things with the other predicates. Second, it is relatively difficult to describe (in comments) why these predicates are important because the if statement makes it difficult to lay out the comments. Third, when you’re deep within the body of the code, it is indented an extra level. Finally, when reading the top of the function, it isn’t clear what the result is if the predicate isn’t true; you have to read to the end of the function to know that it returns null.

【译】如果if的内容很多,这段代码就会出现很多问题。当查看这个函数头时,它只对非结束指令做了些有意思的事情,以及只做了一些判断,这并不会一下子就很清晰。第二点,由于if语句使添加注释变得困难,这就相对很难去描述(注释)为什么这些判断很重要。第三点,当深入到这段代码的内部时,缩进会再增加一个级别。最终,当阅读函数头时,如果判断不正确,搞不清楚结果会是什么;必须读到函数尾才知道返回null

It is much preferred to format the code like this:

【译】最好像这样组织代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Value *doSomething(Instruction *I) {
  // Terminators never need 'something' done to them because ...
  if (I->isTerminator())
    return 0;

  // We conservatively avoid transforming instructions with multiple uses
  // because goats like cheese.
  if (!I->hasOneUse())
    return 0;

  // This is really just here for example.
  if (!doOtherThing(I))
    return 0;

  ... some long code ....
}

This fixes these problems. A similar problem frequently happens in for loops. A silly example is something like this:

【译】这就解决了这些问题。类似的问题也经常发生在for循环里,有个比较笨的例子就像这样:

1
2
3
4
5
6
7
8
9
for (Instruction &I : BB) {
  if (auto *BO = dyn_cast<BinaryOperator>(&I)) {
    Value *LHS = BO->getOperand(0);
    Value *RHS = BO->getOperand(1);
    if (LHS != RHS) {
      ...
    }
  }
}

When you have very, very small loops, this sort of structure is fine. But if it exceeds more than 10-15 lines, it becomes difficult for people to read and understand at a glance. The problem with this sort of code is that it gets very nested very quickly. Meaning that the reader of the code has to keep a lot of context in their brain to remember what is going immediately on in the loop, because they don’t know if/when the if conditions will have elses etc. It is strongly preferred to structure the loop like this:

【译】如果循环体非常非常小,这种结构是可行的。但如果超过10-15行,这很难让人一下子就能读懂。这类代码的问题在于它立刻做很多的嵌套。这意味着,这段代码的读者必须在大脑中保留大量的上下文才能理解循环正在做什么,因为它们不知道是否、何时这个if条件会有个else等等。强烈建议像这样组织循环:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for (Instruction &I : BB) {
  auto *BO = dyn_cast<BinaryOperator>(&I);
  if (!BO) continue;

  Value *LHS = BO->getOperand(0);
  Value *RHS = BO->getOperand(1);
  if (LHS == RHS) continue;

  ...
}

This has all the benefits of using early exits for functions: it reduces nesting of the loop, it makes it easier to describe why the conditions are true, and it makes it obvious to the reader that there is no else coming up that they have to push context into their brain for. If a loop is large, this can be a big understandability win.

【译】这个例子就体现了函数提前退出的优势:减少了循环嵌套;更容易去解释为什么条件是正确;显而易见的告诉读者没有else分支了,无须再为此记住上下文。如果循环很大,那这一定是可理解性的大成功。

Don’t use else after a return(禁止return后使用else)

For similar reasons above (reduction of indentation and easier reading), please do not use 'else' or 'else if' after something that interrupts control flow — like return, break, continue, goto, etc. For example, this is bad:

【译】和上述原因类似(减少缩进、提升可阅读性),请不要在中断控制流的语句后使用elseelse if——例如returnbreakcontinuegoto等。举例来说,这样是不好的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
case 'J': {
  if (Signed) {
    Type = Context.getsigjmp_bufType();
    if (Type.isNull()) {
      Error = ASTContext::GE_Missing_sigjmp_buf;
      return QualType();
    } else {
      break;
    }
  } else {
    Type = Context.getjmp_bufType();
    if (Type.isNull()) {
      Error = ASTContext::GE_Missing_jmp_buf;
      return QualType();
    } else {
      break;
    }
  }
}

It is better to write it like this:

【译】最好这样写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
case 'J':
  if (Signed) {
    Type = Context.getsigjmp_bufType();
    if (Type.isNull()) {
      Error = ASTContext::GE_Missing_sigjmp_buf;
      return QualType();
    }
  } else {
    Type = Context.getjmp_bufType();
    if (Type.isNull()) {
      Error = ASTContext::GE_Missing_jmp_buf;
      return QualType();
    }
  }
  break;

Or better yet (in this case) as:

【译】或者,这个例子更好的写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
case 'J':
  if (Signed)
    Type = Context.getsigjmp_bufType();
  else
    Type = Context.getjmp_bufType();

  if (Type.isNull()) {
    Error = Signed ? ASTContext::GE_Missing_sigjmp_buf :
                     ASTContext::GE_Missing_jmp_buf;
    return QualType();
  }
  break;

The idea is to reduce indentation and the amount of code you have to keep track of when reading the code.

【译】这个思路主要是减少缩进,以及减少阅读时需要跟踪的代码量。

Turn Predicate Loops into Predicate Functions(将判断循环变为判断函数)

It is very common to write small loops that just compute a boolean value. There are a number of ways that people commonly write these, but an example of this sort of thing is:

【译】写个小循环来计算一个布尔值是很常见的。通常会有多种方式来写,但其中一种示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bool FoundFoo = false;
for (unsigned I = 0, E = BarList.size(); I != E; ++I)
  if (BarList[I]->isFoo()) {
    FoundFoo = true;
    break;
  }

if (FoundFoo) {
  ...
}

This sort of code is awkward to write, and is almost always a bad sign. Instead of this sort of loop, we strongly prefer to use a predicate function (which may be static) that uses early exits to compute the predicate. We prefer the code to be structured like this:

【译】这类代码写起来很笨拙,而且几乎就不是个好的写法。除了这类循环,我们强烈推荐使用提前退出来实现这个判断函数(可能是静态函数)。我们期望代码组织成这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/// \returns true if the specified list has an element that is a foo.
static bool containsFoo(const std::vector<Bar*> &List) {
  for (unsigned I = 0, E = List.size(); I != E; ++I)
    if (List[I]->isFoo())
      return true;
  return false;
}
...

if (containsFoo(BarList)) {
  ...
}

There are many reasons for doing this: it reduces indentation and factors out code which can often be shared by other code that checks for the same predicate. More importantly, it forces you to pick a name for the function, and forces you to write a comment for it. In this silly example, this doesn’t add much value. However, if the condition is complex, this can make it a lot easier for the reader to understand the code that queries for this predicate. Instead of being faced with the in-line details of how we check to see if the BarList contains a foo, we can trust the function name and continue reading with better locality.

【译】有很多理由这样设计:减少缩进;提取公共代码,这些代码可以被具有相同判断条件的其他检查代码复用。更重要的是,这会强制去给函数挑个名字,以及强制写注释。在这个笨例子中,并没有体现太多的价差。但是,如果条件很复杂,这会让读者更容易去理解这段代码。我们可以信任这个函数名称,并更好的在当前位置继续阅读,而不用去面对BarList是否包含foo这样内联的检查细节。

The Low-Level Issues(初级主题)

Name Types, Functions, Variables, and Enumerators Properly(类型、函数、变量、枚举的合理命名)

Poorly-chosen names can mislead the reader and cause bugs. We cannot stress enough how important it is to use descriptive names. Pick names that match the semantics and role of the underlying entities, within reason. Avoid abbreviations unless they are well known. After picking a good name, make sure to use consistent capitalization for the name, as inconsistency requires clients to either memorize the APIs or to look it up to find the exact spelling.

【译】不恰当的名称会误导读者并引发错误。我们几乎无法再强调使用描述性的名字是多么重要。在合理的范围内,选择与基础实体语义和角色相匹配的名称。除非非常常用,否则不要使用缩写。选择一个好名字后,确保对名称使用一致的大小写,因为不一致的大小写需要调用者要么记住API或者去查找确切的拼写。

In general, names should be in camel case (e.g. TextFileReader and isLValue()). Different kinds of declarations have different rules:

【译】通常,名称应遵从驼峰命名法(如TextFileReaderisLValue()),不同类型的声明有不同的规则:

  • Type names (including classes, structs, enums, typedefs, etc) should be nouns and start with an upper-case letter (e.g. TextFileReader).

【译】类型名称(包括类、结构体、枚举、typedef等)应该是名词,并以大写字母开头(如TextFileReader)。

  • Variable names should be nouns (as they represent state). The name should be camel case, and start with an upper case letter (e.g. Leader or Boats).

【译】变量名称应该是名词(因为它们代表状态)。名称应该使用驼峰命名法,并以大写字母开头(如LeaderBoats)。

  • Function names should be verb phrases (as they represent actions), and command-like function should be imperative. The name should be camel case, and start with a lower case letter (e.g. openFile() or isFoo()).

【译】函数名称应该是动词短语(因为它们代表动作),命令式函数应该是祈使句。名称应该使用驼峰命名法,并以小写字母开头(如openFile()isFoo())。

  • Enum declarations (e.g. enum Foo {...}) are types, so they should follow the naming conventions for types. A common use for enums is as a discriminator for a union, or an indicator of a subclass. When an enum is used for something like this, it should have a Kind suffix (e.g. ValueKind).

【译】枚举声明(如enum Foo {…})是类型,所以它们应该遵循类型的命名规则。一个枚举的通常用法是联合体的鉴别符或子类的指示符。当枚举用于类似的场景时,它应该有一个Kind后缀(如ValueKind)。

  • Enumerators (e.g. enum { Foo, Bar }) and public member variables should start with an upper-case letter, just like types. Unless the enumerators are defined in their own small namespace or inside a class, enumerators should have a prefix corresponding to the enum declaration name. For example, enum ValueKind { ...}; may contain enumerators like VK_Argument, VK_BasicBlock, etc. Enumerators that are just convenience constants are exempt from the requirement for a prefix. For instance:

【译】枚举成员(如enum { Foo, Bar })和公有成员变量应该以大写字母打头,就像类型。除非枚举成员定义在它们自己的小命名空间或类内部,否则枚举成员应该有一个和枚举名称对应的前缀。例如,enum ValueKind { ...};可以包含枚举成员,如VK_ArgumentVK_BasicBlock等。 仅为了便利而定义的枚举常量不受前缀要求的约束,举例来说:

1
2
3
4
enum {
  MaxSize = 42,
  Density = 12
};

As an exception, classes that mimic STL classes can have member names in STL’s style of lower-case words separated by underscores (e.g. begin(), push_back(), and empty()). Classes that provide multiple iterators should add a singular prefix to begin() and end() (e.g. global_begin() and use_begin()).

【译】例外,模仿STL的类可以拥有STL风格的成员名称:以下划线分隔的全小写单词(如begin()push_back()empty())。提供多种迭代器的类需要为begin()end()添加前缀(如global_begin()use_begin())。

Here are some examples of good and bad names:

【译】以下是一些好命名和不好命名的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class VehicleMaker {
  ...
  Factory<Tire> F;            // Bad -- abbreviation and non-descriptive.
  Factory<Tire> Factory;      // Better.
  Factory<Tire> TireFactory;  // Even better -- if VehicleMaker has more than one
                              // kind of factories.
};

Vehicle makeVehicle(VehicleType Type) {
  VehicleMaker M;                         // Might be OK if having a short life-span.
  Tire Tmp1 = M.makeTire();               // Bad -- 'Tmp1' provides no information.
  Light Headlight = M.makeLight("head");  // Good -- descriptive.
  ...
}

Assert Liberally(灵活的使用断言)

Use the “assert” macro to its fullest. Check all of your preconditions and assumptions, you never know when a bug (not necessarily even yours) might be caught early by an assertion, which reduces debugging time dramatically. The “<cassert>” header file is probably already included by the header files you are using, so it doesn’t cost anything to use it.

【译】充分利用assert宏。检查所有前提条件和假设,因为你永远无法知道在什么时候一个问题(不一定是自己导致的)会被断言提前捕获,这会大大的减少调试时间。<assert>已经被正在使用的头文件所包含,所以使用的话不需要再有额外的动作。

To further assist with debugging, make sure to put some kind of error message in the assertion statement, which is printed if the assertion is tripped. This helps the poor debugger make sense of why an assertion is being made and enforced, and hopefully what to do about it. Here is one complete example:

【译】为进一步辅助调试,确保在断言语句中添加某种错误信息,如果断言被触发,则会打印该错误信息。这有助于缺乏经验的调试者理解为什么要定并强制执行一个断言,并期望如何去处理它。下面是个完整的例子:

1
2
3
4
inline Value *getOperand(unsigned I) {
  assert(I < Operands.size() && "getOperand() out of range!");
  return Operands[I];
}

Here are more examples:

【译】更多例子:

1
2
3
4
5
6
7
8
9
assert(Ty->isPointerType() && "Can't allocate a non-pointer type!");

assert((Opcode == Shl || Opcode == Shr) && "ShiftInst Opcode invalid!");

assert(idx < getNumSuccessors() && "Successor # out of range!");

assert(V1.getType() == V2.getType() && "Constant types must be identical!");

assert(isa<PHINode>(Succ->front()) && "Only works on PHId BBs!");

You get the idea.

【译】这下你应该懂了。

In the past, asserts were used to indicate a piece of code that should not be reached. These were typically of the form:

【译】过去,断言用于表示不可达的代码片段,通常形式如下:

1
assert(0 && "Invalid radix for integer literal");

This has a few issues, the main one being that some compilers might not understand the assertion, or warn about a missing return in builds where assertions are compiled out.

【译】这存在一些问题,主要的一个是一些编译器无法解析断言,或编译时断言被编译掉了,会提示缺少返回值。

Today, we have something much better: llvm_unreachable:

如今我们有更好的选择:llvm_unreachable

1
llvm_unreachable("Invalid radix for integer literal");

When assertions are enabled, this will print the message if it’s ever reached and then exit the program. When assertions are disabled (i.e. in release builds), llvm_unreachable becomes a hint to compilers to skip generating code for this branch. If the compiler does not support this, it will fall back to the “abort” implementation.

【译】启用断言后,如果运行到这段代码,就会打印这段信息并退出程序。禁用断言后(如编译发布版本),llvm_unreachable成为编译器跳过为此分支生成代码的提示。如果编译器不支持该特性,它将回退成abort实现。

Neither assertions or llvm_unreachable will abort the program on a release build. If the error condition can be triggered by user input then the recoverable error mechanism described in LLVM Programmer’s Manual should be used instead. In cases where this is not practical, report_fatal_error may be used.

【译】在发布版本中,无论断言还是llvm_unreachable都将中止程序。如果出错条件可以由用户输入触发,则应使用LLVM程序员手册中描述的可恢复错误机制。 如果这不可行,可以使用report_fatal_error。

Another issue is that values used only by assertions will produce an “unused value” warning when assertions are disabled. For example, this code will warn:

【译】还有个问题,如果禁用断言,仅由断言使用的值将产生“未使用的值”警告。例如,以下代码将告警:

1
2
3
4
5
unsigned Size = V.size();
assert(Size > 42 && "Vector smaller than it should be");

bool NewToSet = Myset.insert(Value);
assert(NewToSet && "The value shouldn't be in the set yet");

These are two interesting different cases. In the first case, the call to V.size() is only useful for the assert, and we don’t want it executed when assertions are disabled. Code like this should move the call into the assert itself. In the second case, the side effects of the call must happen whether the assert is enabled or not. In this case, the value should be cast to void to disable the warning. To be specific, it is preferred to write the code like this:

【译】这两个例子很有意思。第一个例子中,对V.size()的调用仅对断言有效,我们并不希望在禁用断言的情况下执行。像这样的代码应该把调用移到断言内。第二个例子中,无论断言是否启用,调用的副作用都会产生。这种情况下,应将值转换为void来禁用告警。具体来说,最好这样编码:

1
2
3
4
assert(V.size() > 42 && "Vector smaller than it should be");

bool NewToSet = Myset.insert(Value); (void)NewToSet;
assert(NewToSet && "The value shouldn't be in the set yet");

Do Not Use using namespace std(禁用using namespace std)

In LLVM, we prefer to explicitly prefix all identifiers from the standard namespace with an “std::” prefix, rather than rely on “using namespace std;”.

【译】在LLVM中,我们更喜欢使用“std ::”前缀显式地为标准命名空间中的所有标识符添加前缀,而不是依赖于“using namespace std;”。

In header files, adding a 'using namespace XXX' directive pollutes the namespace of any source file that #includes the header. This is clearly a bad thing.

【译】在头文件中,使用'using namespace XXX'会污染任何#include该头文件的源文件命名空间。 这显然不好。

In implementation files (e.g. .cpp files), the rule is more of a stylistic rule, but is still important. Basically, using explicit namespace prefixes makes the code clearer, because it is immediately obvious what facilities are being used and where they are coming from. And more portable, because namespace clashes cannot occur between LLVM code and other namespaces. The portability rule is important because different standard library implementations expose different symbols (potentially ones they shouldn’t), and future revisions to the C++ standard will add more symbols to the std namespace. As such, we never use 'using namespace std;' in LLVM.

【译】在实现文件(如.cpp文件)中,这条规范更像是一种格式规范,但依然很重要。 基本上,使用显式命名空间前缀可以使代码更清晰,因为用了什么、在哪儿定义将会变得显而易见。 而且可以避免LLVM代码和其他命名空间之间发生命名空间冲突,代码更具可移植性。 可移植性规则很重要,因为不同的标准库实现对外暴露了不同的符号(可能有些不应该暴露),随着将来对C ++标准的修订,std命名空间中将会添加更多符号。 因此,我们从不在LLVM中使用’using namespace std;' 。

The exception to the general rule (i.e. it’s not an exception for the std namespace) is for implementation files. For example, all of the code in the LLVM project implements code that lives in the ‘llvm’ namespace. As such, it is ok, and actually clearer, for the .cpp files to have a 'using namespace llvm;' directive at the top, after the #includes. This reduces indentation in the body of the file for source editors that indent based on braces, and keeps the conceptual context cleaner. The general form of this rule is that any .cpp file that implements code in any namespace may use that namespace (and its parents’), but should not use any others.

【译】针对实现文件,也有例外(即,它不是针对std命名空间的例外)。 例如,在LLVM工程中,所有代码都实现了位于“llvm”命名空间中的代码。 因此,对于.cpp文件来说,使用’using namespace llvm;‘是可以的,而且实际上更清楚。 在#includes之后的顶部指令。 这减少了基于大括号缩进的源编辑器的文件正文中的缩进,并使概念上下文更清晰。 此规则的一般形式是,在任何命名空间中实现代码的任何.cpp文件都可以使用该命名空间(及其父节点),但不应使用任何其他命名空间。

Provide a Virtual Method Anchor for Classes in Headers(为定义在头文件中的类提供虚函数声明)

If a class is defined in a header file and has a vtable (either it has virtual methods or it derives from classes with virtual methods), it must always have at least one out-of-line virtual method in the class. Without this, the compiler will copy the vtable and RTTI into every .o file that #includes the header, bloating .o file sizes and increasing link times.

【译】如果一个类定义在头文件中,并具有虚函数表(无论含有虚函数还是继承自含有虚函数的类),那它必然会有至少一个外联的虚函数。如果没有这个声明,编译器会将虚函数表和RTTI复制到包含此头文件的每个.o文件中,这将增加.o文件的大小和链接时间。

Don’t use default labels in fully covered switches over enumerations(枚举成员全覆盖的switch分支中,不要使用default标签)

-Wswitch warns if a switch, without a default label, over an enumeration does not cover every enumeration value. If you write a default label on a fully covered switch over an enumeration then the -Wswitch warning won’t fire when new elements are added to that enumeration. To help avoid adding these kinds of defaults, Clang has the warning -Wcovered-switch-default which is off by default but turned on when building LLVM with a version of Clang that supports the warning.

【译】对枚举类型进行switch分支选择,如果没有覆盖所有枚举值,且无default标签,则会产生-Wswitch告警。如果为枚举成员全覆盖的switch添加default标签,那么当在枚举类型中添加新成员时,-Wswitch告警就不会触发。为了避免添加这类default,Clang有一个默认关闭的-Wcovered-switch-default告警,它会在使用支持此告警的Clang版本构建LLVM时打开。

A knock-on effect of this stylistic requirement is that when building LLVM with GCC you may get warnings related to “control may reach end of non-void function” if you return from each case of a covered switch-over-enum because GCC assumes that the enum expression may take any representable value, not just those of individual enumerators. To suppress this warning, use llvm_unreachable after the switch.

【译】这种风格的规定带来了一个连锁效应,当使用GCC构建LLVM时,如果枚举成员全覆盖的switch的每个case分支均执行return,那将触发和“control may reach end of non-void function”相关的告警。这是因为GCC假定枚举表达式可能包含任何可表示的值,而不仅仅是一个个枚举成员。要避免该告警,在switch分支选择完成后,使用llvm_unreachable

Use range-based for loops wherever possible(尽可能使用基于范围的for循环)

The introduction of range-based for loops in C++11 means that explicit manipulation of iterators is rarely necessary. We use range-based for loops wherever possible for all newly added code. For example:

【译】在C ++ 11中引入了基于范围的for循环,这意味着大多数情况下,不需要显式操作迭代器。 我们应尽可能的在所有新增代码重使用基于范围的for循环。例如:

1
2
3
BasicBlock *BB = ...
for (Instruction &I : *BB)
  ... use I ...

Don’t evaluate end() every time through a loop(不要每轮循环都计算end())

In cases where range-based for loops can’t be used and it is necessary to write an explicit iterator-based loop, pay close attention to whether end() is re-evaluted on each loop iteration. One common mistake is to write a loop in this style:

【译】如果无法使用基于范围的for循环,而且有必要写显式迭代器的循环,那就要密切注意是否在每次循环迭代时重新计算end()。 一个常见的错误是以下面这种方式写循环:

1
2
3
BasicBlock *BB = ...
for (auto I = BB->begin(); I != BB->end(); ++I)
  ... use I ...

The problem with this construct is that it evaluates “BB->end()” every time through the loop. Instead of writing the loop like this, we strongly prefer loops to be written so that they evaluate it once before the loop starts. A convenient way to do this is like so:

【译】这种结构的问题在于每次循环迭代都会计算“BB->end()”。为了不写这样的循环,强烈建议在循环开始前就计算好它。一个不错的实现方式是这样的:

1
2
3
BasicBlock *BB = ...
for (auto I = BB->begin(), E = BB->end(); I != E; ++I)
  ... use I ...

The observant may quickly point out that these two loops may have different semantics: if the container (a basic block in this case) is being mutated, then “BB->end()” may change its value every time through the loop and the second loop may not in fact be correct. If you actually do depend on this behavior, please write the loop in the first form and add a comment indicating that you did it intentionally.

【译】读者可能会很快指出这两个循环可能有不同的语义:如果容器(在本例中是一个基础块)正在被修改,那么每次循环迭代后“BB->end()”的值都可能发生改变,可能第二次循环就已经不正确了。 如果确实有这样的行为,那请用第一种形式写循环,并且添加注释来说明就是要这样做。

Why do we prefer the second form (when correct)? Writing the loop in the first form has two problems. First it may be less efficient than evaluating it at the start of the loop. In this case, the cost is probably minor — a few extra loads every time through the loop. However, if the base expression is more complex, then the cost can rise quickly. I’ve seen loops where the end expression was actually something like: “SomeMap[X]->end()” and map lookups really aren’t cheap. By writing it in the second form consistently, you eliminate the issue entirely and don’t even have to think about it.

【译】为什么倾向于第二种形式(正确的时候)?以第一种形式写循环有两个问题。 首先,它可能会比在循环开始就计算的效率低。 这种情况下,代价可能很小 —— 每次循环都会做一些额外加载。 但是,如果基础表达式变得更复杂,那么代价将会迅速增加。 我见过一些循环,它们的结束表达式实际上像这样:“SomeMap[X]->end()”,而在map中查找的代价真的不小。 通过统一按第二种形式写,这个问题就完全消除了,甚至都不需要考虑。

The second (even bigger) issue is that writing the loop in the first form hints to the reader that the loop is mutating the container (a fact that a comment would handily confirm!). If you write the loop in the second form, it is immediately obvious without even looking at the body of the loop that the container isn’t being modified, which makes it easier to read the code and understand what it does.

【译】第二个(甚至更为严重)的问题是,以第一种形式写循环是在提醒读者:循环正在改变容器(注释可以轻易确认的事实!)。 如果以第二种形式写循环,一目了然,甚至都不用看循环体是否修改了容器,这使得阅读和理解代码变得更简单。

While the second form of the loop is a few extra keystrokes, we do strongly prefer it.

【译】虽然第二种形式需要打更多的字,但我们确实很推崇它。

#include <iostream> is Forbidden(禁用#include <iostream>)

The use of #include <iostream> in library files is hereby forbidden, because many common implementations transparently inject a static constructor into every translation unit that includes it.

【译】特别声明,禁止在库文件中使用#include <iostream>,因为通常的实现方式是显式将静态构造函数注入到每个包含它的转换单元中。

Note that using the other stream headers (<sstream> for example) is not problematic in this regard — just <iostream>. However, raw_ostream provides various APIs that are better performing for almost every use thanstd::ostream style APIs.

【译】请注意,这仅针对<iostream>,其他流的头文件(例如<sstream>)在这方面是没有问题的。不过,raw_ostream提供了各种各样的API,几乎每个都比std::ostream样式的更好用。

Note(注意)

New code should always use raw_ostream for writing, or the llvm::MemoryBuffer API for reading files.

【译】新增代码应该始终用raw_ostream,或使用llvm::MemoryBuffer接口读文件。

Use raw_ostream(使用raw_ostream)

LLVM includes a lightweight, simple, and efficient stream implementation in llvm/Support/raw_ostream.h, which provides all of the common features of std::ostream. All new code should use raw_ostream instead of ostream.

【译】LLVM在llvm/Support/raw_ostream.h中包含了一个轻量级、简单并且高效的流实现,它提供了std::ostream的所有通用特性。所有新增代码都应该使用raw_ostream而不是ostream

Unlike std::ostream, raw_ostream is not a template and can be forward declared as class raw_ostream. Public headers should generally not include the raw_ostream header, but use forward declarations and constant references to raw_ostream instances.

【译】与std::ostream不同,raw_ostream不是模板,并可以前向声明为class raw_ostream。公共头文件通常不应该包含raw_ostream头文件,但可以用前向声明,以及raw_ostream实例的常量引用。

Avoid std::endl(避免使用std::endl)

The std::endl modifier, when used with iostreams outputs a newline to the output stream specified. In addition to doing this, however, it also flushes the output stream. In other words, these are equivalent:

【译】当与iostreams一起使用时,std::endl修饰符会向指定的输出流中输出一个换行符。而且除了这个,它还是会刷新输出流缓冲区,等于这样:

1
2
std::cout << std::endl;
std::cout << '\n' << std::flush;

Most of the time, you probably have no reason to flush the output stream, so it’s better to use a literal '\n'.

【译】大多数情况下,可能没有什么理由去刷新输出流缓冲区,最好使用'\n'

Don’t use inline when defining a function in a class definition(不需要对类的成员函数使用inline)

A member function defined in a class definition is implicitly inline, so don’t put the inline keyword in this case.

【译】类的成员函数默认隐式内联,所以在这种情况下不需要使用inline关键字。

Don’t:

【译】不要这样做:

1
2
3
4
5
6
class Foo {
public:
  inline void bar() {
    // ...
  }
};

Do:

【译】这样做:

1
2
3
4
5
6
class Foo {
public:
  void bar() {
    // ...
  }
};

Microscopic Details(小细节)

This section describes preferred low-level formatting guidelines along with reasoning on why we prefer them.

【译】本节描述了更为初级的格式化指导,以及为什么更偏向于使用它们。

Spaces Before Parentheses(括号前的空格)

We prefer to put a space before an open parenthesis only in control flow statements, but not in normal function call expressions and function-like macros. For example, this is good:

【译】我们倾向于在控制流语句的括号前加一个空格,在普通函数调用表达式以及函数似的宏前则不加。例如,这样可以:

1
2
3
4
5
6
7
8
if (X) ...
for (I = 0; I != 100; ++I) ...
while (LLVMRocks) ...

somefunc(42);
assert(3 != 4 && "laws of math are failing me");

A = foo(42, 92) + bar(X);

and this is bad:

【译】这样不行:

1
2
3
4
5
6
7
8
if(X) ...
for(I = 0; I != 100; ++I) ...
while(LLVMRocks) ...

somefunc (42);
assert (3 != 4 && "laws of math are failing me");

A = foo (42, 92) + bar (X);

The reason for doing this is not completely arbitrary. This style makes control flow operators stand out more, and makes expressions flow better. The function call operator binds very tightly as a postfix operator. Putting a space after a function name (as in the last example) makes it appear that the code might bind the arguments of the left-hand-side of a binary operator with the argument list of a function and the name of the right side. More specifically, it is easy to misread the “A” example as:

【译】这样做的原因并不完全是随意的。 这种风格使控制流操作显得更加突出,使之可以更好的表达。 函数调用运算符作为后缀运算符连接的非常紧密。 在函数名后添加一个空格(如上例所示)会让代码看起来就像,函数的参数列表作为二元运算符左侧的参数和右侧的名称绑定在一起。 更具体地说,很容易将“A”示例误读成下面这样:

1
A = foo ((42, 92) + bar) (X);

when skimming through the code. By avoiding a space in a function, we avoid this misinterpretation.

【译】通过不在函数中的添加这样的空格,我们就避免了这种误解。

Prefer Preincrement(优先使用前自增)

Hard fast rule: Preincrement (++X) may be no slower than postincrement (X++) and could very well be a lot faster than it. Use preincrementation whenever possible.

【译】规范概览:前自增(++X)可能不比后自增(X++)慢,并且可能比它快很多。 尽可能使用前自增。

The semantics of postincrement include making a copy of the value being incremented, returning it, and then preincrementing the “work value”. For primitive types, this isn’t a big deal. But for iterators, it can be a huge issue (for example, some iterators contains stack and set objects in them… copying an iterator could invoke the copy ctor’s of these as well). In general, get in the habit of always using preincrement, and you won’t have a problem.

【译】后自增的语义包含拷贝待自增的值,返回它,然后前自增“工作值”。 对于原生类型,这不是什么大问题。 但对于迭代器来说,它可能是一个巨大的问题(例如,一些迭代器包含栈和集合对象…复制迭代器可能会同时调用这些迭代器的拷贝构造函数)。 一般来说,养成一直使用前自增的习惯就不会有问题了。

Namespace Indentation(命名空间的缩进)

In general, we strive to reduce indentation wherever possible. This is useful because we want code to fit into 80 columns without wrapping horribly, but also because it makes it easier to understand the code. To facilitate this and avoid some insanely deep nesting on occasion, don’t indent namespaces. If it helps readability, feel free to add a comment indicating what namespace is being closed by a }. For example:

【译】一般来讲,我们尽可能地努力减少缩进。 这很有用,因为我们希望代码可以适配80列,而且没什么不友好的东西包住它,也因为这会使代码更容易理解。 为了方便做到这一点,并避免一些疯狂的深度嵌套,那就不对命名空间缩进。 如果在闭合命名空间的}处添加命名空间注释,可以帮助提高可读性,尽管去做。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace llvm {
namespace knowledge {

/// This class represents things that Smith can have an intimate
/// understanding of and contains the data associated with it.
class Grokable {
...
public:
  explicit Grokable() { ... }
  virtual ~Grokable() = 0;

  ...

};

} // end namespace knowledge
} // end namespace llvm

Feel free to skip the closing comment when the namespace being closed is obvious for any reason. For example, the outer-most namespace in a header file is rarely a source of confusion. But namespaces both anonymous and named in source files that are being closed half way through the file probably could use clarification.

【译】无论出于什么原因,当闭合的命名空间很明显时,可以随意跳过闭合注释。 例如,头文件中最外层的命名空间很少会引起混淆。 但是在源文件中,匿名和具名的命名空间可能会在文件的中途被闭合,所以需要使用注释。

Anonymous Namespaces(匿名空间的缩进)

After talking about namespaces in general, you may be wondering about anonymous namespaces in particular. Anonymous namespaces are a great language feature that tells the C++ compiler that the contents of the namespace are only visible within the current translation unit, allowing more aggressive optimization and eliminating the possibility of symbol name collisions. Anonymous namespaces are to C++ as “static” is to C functions and global variables. While “static” is available in C++, anonymous namespaces are more general: they can make entire classes private to a file.

【译】聊过通常的命名空间后,你可能会特别想知道匿名空间。 匿名空间是一种很好的语言特性,它告诉C++编译器,该命名空间的内容仅在当前转换单元中可见,可以使用更激进的优化,同时可以消除符号名称冲突的可能性。 匿名空间对于C++,就像“static”对于C函数和全局变量。 虽然C++中提供了“static”,但匿名空间更为通用:它们可以使整个类变为文件私有。

The problem with anonymous namespaces is that they naturally want to encourage indentation of their body, and they reduce locality of reference: if you see a random function definition in a C++ file, it is easy to see if it is marked static, but seeing if it is in an anonymous namespace requires scanning a big chunk of the file.

【译】匿名空间的问题在于,它们很自然的希望更多的对内容进行缩进,以及减少了引用的局部性:如果在C ++文件中看到随便一个函数定义,很容易看出它是否标记为static,但是判断是否位于匿名命名空间中,则需要阅读文件的一大段。

Because of this, we have a simple guideline: make anonymous namespaces as small as possible, and only use them for class declarations. For example, this is good:

【译】因此,我们有一个简单的准则:使匿名空间尽可能小,并且只将它们用于类声明。 例如,这就很好:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
namespace {
class StringSort {
...
public:
  StringSort(...)
  bool operator<(const char *RHS) const;
};
} // end anonymous namespace

static void runHelper() {
  ...
}

bool StringSort::operator<(const char *RHS) const {
  ...
}

This is bad:

【译】这就不行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace {

class StringSort {
...
public:
  StringSort(...)
  bool operator<(const char *RHS) const;
};

void runHelper() {
  ...
}

bool StringSort::operator<(const char *RHS) const {
  ...
}

} // end anonymous namespace

This is bad specifically because if you’re looking at “runHelper” in the middle of a large C++ file, that you have no immediate way to tell if it is local to the file. When it is marked static explicitly, this is immediately obvious. Also, there is no reason to enclose the definition of “operator<” in the namespace just because it was declared there.

【译】如果在一个庞大C ++文件的中间查看“runHelper”,但没法立即判断出它是否在这个文件中,这就会非常糟糕。 当显示标记为static时,这就很明显。 此外,没有理由将“operator <”的定义限制在匿名空间中,仅仅是因为是在那里声明的。

See Also

A lot of these comments and recommendations have been culled from other sources. Two particularly important books for our work are:

  1. Effective C++ by Scott Meyers. Also interesting and useful are “More Effective C++” and “Effective STL” by the same author.
  2. Large-Scale C++ Software Design by John Lakos

If you get some free time, and you haven’t read them: do so, you might learn something.

© Copyright 2003-2019, LLVM Project. Last updated on 2019-06-05. Created using Sphinx 1.7.6.

译注