下面是GoogleC++StyleGuide中的HeaderFiles部分原文的个人总结,提供了更具体的例子。
头文件和实现对应每个.cc文件通常都应该有一个关联的.h文件。这是因为.h文件通常包含了.cc文件中使用的函数和类的声明。
例如,如果你有一个名为MyClass的类,那么你可能会有一个名为的头文件和一个名为的源文件。
//_HclassMyClass{public:MyClass();voidmyMethod();};include""MyClass::MyClass(){//constructorimplementation}voidMyClass::myMethod(){//methodimplementation}有一些常见的例外,例如单元测试和只包含main()函数的小.cc文件。这些文件可能不需要关联的.h文件。例如,一个只包含main()函数的.cc文件可能看起来像这样:
这种方式的一个主要问题是,.inc文件可能不是自包含的,也就是说,它可能依赖于在包含它的文件中先前定义或包含的一些内容。这使得代码的组织和理解变得更加困难,因此,一般建议尽可能少地使用.inc文件,而更倾向于使用自包含的头文件。
假设我们有一个文件,它依赖于一个在其他地方定义的宏MULTIPLY:
//(x,y)((x)*(y))include""doublesquare(doublex){returnSQUARE(x);}那么,当我们尝试编译my_other_时,就会出现错误,因为SQUARE宏依赖的MULTIPLY宏在这个文件中没有定义。
这就是.inc文件可能存在的问题:它们可能不是自包含的,也就是说,它们可能依赖于在包含它们的文件中先前定义或包含的一些内容。这使得代码的组织和理解变得更加困难,因为你需要知道每个.inc文件的依赖关系,以及在哪里和如何使用它们。
因此,一般建议尽可能少地使用.inc文件,而更倾向于使用自包含的头文件。自包含的头文件是指该头文件包含了它所需要的所有依赖,因此你可以在任何地方安全地包含它,而不需要担心依赖问题。
直接包含所使用的符号如果一个源文件或头文件引用了在其他地方定义的符号,那么这个文件应该直接包含一个头文件,这个头文件明确地提供了这个符号的声明或定义。例如,如果你的文件使用了std::vector,那么它应该直接包含vector,而不是依赖于其他头文件间接地包含vector。
//语句,而不会破坏客户端的代码。例如,即使已经包含了,如果使用了中的符号,那么也应该直接包含。//""include语句而导致的编译错误。什么时候使用内联函数?可以通过inline关键字声明一个函数,这样编译器可以将这个函数展开为内联代码,而不是通过常规的函数调用机制调用它。例如:
inlineintadd(inta,intb){returna+b;}只有当函数很小(例如,10行或更少)时,才应该将其定义为内联函数。内联函数可以生成更高效的目标代码,但是过度使用内联函数实际上可能会使程序运行得更慢。因此,你应该只对访问器(accessors)、修改器(mutators)和其他短小、对性能要求高的函数使用内联。
classMyClass{private:intmyValue;public://Accessor(getter)inlineintgetValue()const{returnmyValue;}//Mutator(setter)inlinevoidsetValue(intvalue){myValue=value;}};在这个例子中,getValue和setValue函数都是内联函数。因为它们都很小(只有一行代码),并且可能会被频繁地调用,所以将它们定义为内联函数可以提高程序的性能。
然而,如果一个函数的代码很长,或者包含了复杂的逻辑,那么将其定义为内联函数可能会导致生成的代码体积增大,从而降低程序的性能。例如,以下的函数就不应该被定义为内联函数:
inlinevoidprintLotsOfStuff(){for(inti=0;i1000;++i){std::cout"Thisisline"istd::l;}}在这个例子中,printLotsOfStuff函数包含了一个循环,会打印出1000行文本。如果将这个函数定义为内联函数,那么每次调用这个函数时,编译器都会插入这1000行代码,从而导致生成的代码体积大大增加。因此,这个函数不应该被定义为内联函数。
通常,如果一个函数包含循环或switch语句,那么将其内联可能不会提高性能。此外,虚函数和递归函数通常也不应该被内联。虚函数之所以被声明为内联,主要是为了将其定义放在类中,以便于查看或者记录其行为。
在这个例子中,Base::foo是一个虚函数,它被声明为内联是为了将其定义放在类中,以便于查看或者记录其行为。然而,由于它是一个虚函数,所以在运行时,编译器通常不会将其内联。
递归函数通常不应该被内联。例如:
inlineintfactorial(intn){if(n==0){return1;}else{returnn*factorial(n-1);}}在这个例子中,factorial是一个递归函数,如果将这个函数定义为内联函数,那么每次调用这个函数时,编译器都会插入这个函数的代码,从而可能导致生成的代码体积增大,降低程序的性能。
虚函数通常不应该被内联,除非你想将其定义放在类中,以便于查看或者记录其行为。例如:
classBase{public:virtualinlinevoidfoo(){std::cout"Base::foo"std::l;}};classDerived:publicBase{public:voidfoo()override{std::cout"Derived::foo"std::l;}};在这个例子中,Base::foo是一个虚函数,它被声明为内联是为了将其定义放在类中,以便于查看或者记录其行为。然而,由于它是一个虚函数,所以在运行时,编译器通常不会将其内联。
头文件包含顺序接下来提供了一种推荐的头文件包含顺序,以及如何在不同情况下使用引号("")和尖括号()来包含头文件。
以下是一个具体的代码示例,展示了这些规则的应用:
//foo/server///Relatedheaderincludesys/"third_party/absl/flags/"//Yourproject'sheadersinclude"foo/server/"在这个例子中,首先包含了与当前源文件最相关的头文件foo/server/。然后,按照C系统头文件、C++标准库头文件、其他库的头文件、项目的头文件的顺序包含了其他头文件。每一组非空的头文件之间都用一个空行隔开。
这种头文件包含顺序的好处是,如果相关的头文件foo/server/遗漏了任何必要的包含,那么在构建foo/server/时就会出现错误。这样,这个规则确保了构建错误首先会出现在正在处理这些文件的人那里,而不是在其他无辜的包中。
在每个部分中,头文件应按字母顺序排列。注意,旧的代码可能不符合这个规则,应在方便的时候修复。
有时,系统特定的代码需要条件包含。这样的代码可以在其他包含之后放置条件包含。
include"base/"//ForLANG__list#if//LANG_CXX11在这个例子中,initializer_list只在LANG_CXX11被定义时才被包含。然而,这种做法也有一些缺点。首先,它会使你的代码更复杂,更难以理解和维护。其次,它可能会导致你的代码在不同的环境中表现不同,这可能会引入一些难以发现的bug。因此,应该尽量减少和局部化你的系统特定代码。





