Felomeng翻译:Google C++ 编程规范——作用域收藏 function change_alt1(btn,style){var btn=document.getElementById(btn);btn.style.display = style;}此文于2011-03-24被推荐到CSDN首页如何被推荐?

阅读更多

与官方翻译版本(http://code.google.com/p/zh-google-styleguide/downloads/list)不同,本文为本人原创翻译。


1 作用域


1.1 命名空间namespace


.cc中提倡使用匿名命名空间(unnamed namespace[i])。而定名命名空间(named namespace)的命名应该以项目及(如果不在根目录的话)项目中的路径来命名。不要使用using关键字。


定义:命名空间将作用域分割为相互独立的,具有特定名称的作用域。这样就可以避免在全局作用域中容易产生的同名冲突。


优点:在类提供的(有层次的)名称隔离方法基础上提供了另一套(有层次的)命名隔离方法[ii]


例如,两个不同的工程中都含有全局类Foo,这两个类名可能在编译时或者运行时产生同名冲突。但是,如果在每个工程中都将他们放进相应的命名空间中,project1::Fooproject2::Foo就是两个完全不同的类名,而不会再产生同名冲突了。


劣势:命名空间容易让人感到迷惑,因为类本身已经提供了一套(有层次的)名称隔离方法,而命名空间在这个基础上又添加了一套(有层次的)名称隔离方法。


在头文件中使用匿名命名空间,很容易违反C++的单一定义规则(One Definition Rule[iii])。


结论:根据下述原则使用命名空间。


匿名命名空间


.cc文件中,不仅允许使用匿名命名空间,并且,为了避免运行时同名冲突,还提倡使用匿名命名空间:



· namespace { // This is in a .cc file.


·


· // The content of a namespace is not indented


· enum { kUnused, kEOF, kError }; // Commonly used tokens.


· bool AtEof() { return pos_ == kEOF; } // Uses our namespace's EOF.


·




} // namespace



但是,文件内的与某个特定类相关的实体,应当在该类内声明为类型、静态成员或静态成员函数,而不是在类外声明为匿名命名空间的一个成员。如上例所示,在匿名空间的结尾处要添加注释“// namespace”。


.h文件中不要使用匿名命名空间。


定名命名空间


定名命名空间用法如下:


除文件最前端的包含语句、gflags[iv]定义(声明)以及前置声明的别的命名空间中的类之外,命名空间包含整个源代码文件的内容:



· // In the .h file


· namespace mynamespace {


·


· // All declarations are within the namespace scope.


· // Notice the lack of indentation.


· class MyClass {


· public:


· ...


· void Foo();


· };


·




} // namespace mynamespace


// In the .cc file


namespace mynamespace {



// Definition of functions is within scope of the namespace.


void MyClass::Foo() {


...


}



} // namespace mynamespace



典型的.cc文件可能会非常复杂,还有可能引用到别的命名空间中的类。



#include "a.h"



DEFINE_bool(someflag, false, "dummy flag");



class C; // Forward declaration of class C in the global namespace.


namespace a { class A; } // Forward declaration of a::A.



namespace b {



...code for b... // Code goes against the left margin.



} // namespace b



不要在std命名空间内定义声明内容,包括标准库中类的前置声明。在命名空间中声明任何实体都是未定义行为(undefined behavior [v]),就是说不可移植。声明标准库中实体的方法是包含相应的头文件。


没有必要使用using语句让命名空间内所有的实体名都可用。



· // Forbidden -- This pollutes the namespace.




using namespace foo;



.cc文件中的任何位置都可以使用using语句,在.h文件中的函数、方法和类内可以使用using语句。



· // OK in .cc files.


· // Must be in a function, method or class in .h files.




using ::foo::bar;



.cc文件内的任何地方,包含整体文件的定名命名实体内的任何地方,以及函数和方法内部都可以使用命名实体别名(namespace aliases)。



· // Shorten access to some commonly used names in .cc files.


· namespace fbz = ::foo::bar::baz;


·


· // Shorten access to some commonly used names (in a .h file).


· namespace librarian {


· // The following alias is available to all files including


· // this header (in namespace librarian):


· // alias names should therefore be chosen consistently


· // within a project.


· namespace pd_s = ::pipeline_diagnostics::sidetable;


·


· inline void my_inline_function() {


· // namespace alias local to a function (or method).


· namespace fbz = ::foo::bar::baz;


· ...


· }




} // namespace librarian



注意,在头文件中定义的别名,在任何包含这个头文件的文件中都是可见的。所以在公共头文件中(这些头文件可以在工程外被包含),以及间接地被这些公共头文件包含的头文件中,应当避免使用别名。这也是保持API尽量简洁总目标的一部分。


1.2 内嵌类(Nested Class


虽然,当内嵌类是接口的一部分时,可以声明为public,但最好还是使用命名空间将内嵌类声明与全局作用域隔离。


定义:在类内部定义的另一个类;这个内部的类也称为成员类(member class)。



class Foo {



private:


// Bar is a member class, nested within Foo.


class Bar {


...


};



};



优点:当只有内嵌类的外层类(enclosing class)使用它时很有用,这时可以将它定义为外层类的成员,从而把它的作用域限定在外层类的内部,避免声明在外部作用域所带来的名称污染(即不会影响到用不到其他的类)。内嵌类可以在外层类中前置声明,然后在.cc文件中定义。这样一来,就可以避免在外层类的定义中定义整个内嵌类,而且内嵌类一般也只与实现相关。


劣势:内嵌类只能在外层类的定义内前置声明。所以,任何使用Foo::Bar*指针的头文件都需要包含Foo类的完整声明。


结论:除非内嵌类是接口的一部分,否则不要声明为public的,例如,一个类包含某方法的多个选项。


1.3 非成员函数(Nonmember)、静态成员函数( Static Member)、全局函数(Global Functions


优先使用命名空间内的非成员函数或静态成员函数,尽量不要使用全局函数。


优点:有时,非成员函数和静态成员函数很有用。将非成员函数放在某命名空间内,可以防止其对全局命名空间的名称污染。


劣势:将非成员函数和静态函数作为一个新类的成员函数可能更合理,尤其当这些函数访问大量外部资源或具有大量依赖时。


结论:有时,定义一个类外函数是有用的,甚至是必须的。这样的函数可以是一个静态成员函数或者非成员函数。非成员函数不应依赖外部变量,并且应当总是只存在于一个命名空间内。不要用类把不含共享静态数据的静态成员函数封装起来,把这样的函数放到空间


和其他类在同一编译单元内定义的函数,如果在别的编译单元中直接调用,可能会引起额外的耦合和连接依赖,静态成员函数尤其明显。这时,应当考虑建立一个新类来封装这些函数,也可以将这些函数封装到独立类库的命名空间中。


如果必须定义一个非成员函数,并且只在当前.cc文件中使用,使用匿名命名空间或者static连接关键字(比如static int Foo() {...})来限定它的作用域。


1.4 局部变量(Local Variable


将函数中的变量作用域限定在尽量小的范围内,并且在定义时就初始化。


C++允许在函数内任何地方声明变量。但是,要在尽量小的作用域中声明变量,最好在第一次使用前声明。这样,读代码的人就很容易找到定义,从而知道这个变量是什么类型,初始值是多少。这里要特别强调:定义和初始化应放在同一语句,而不是先定义后再单独进行赋值。



int i;


i = f(); // Bad -- initialization separate from declaration.




int j = g(); // Good -- declaration has initialization.



值得注意的是,gcc中对for (int i = 0; i < 10; ++i)语句的实现是正确的(变量i的作用域限定在for循环内)。因此,在与这个for循环处于同一作用域中的其他for循环中还可以使用变量igccif语句和while语句中声明的变量的作用域的实现也是正确的。比如:



while (const char* p = strchr(str, '/')) str = p + 1;



警告:如果变量是一个对象,那么在每次进入作用域时都调用构造函数并创建这个对象,而在出了作用域之后又会调用析构函数来销毁这个对象。



// Inefficient implementation:


for (int i = 0; i < 1000000; ++i) {


Foo f; // My ctor and dtor get called 1000000 times each.


f.DoSomething(i);


}



可以将这样的变量声明在循环体外来提高效率。



Foo f; // My ctor and dtor get called once each.


for (int i = 0; i < 1000000; ++i) {


f.DoSomething(i);


}



1.5 静态变量(Static Variable)和全局变量(Global Variable


禁止使用静态或全局的类类型变量:它们会引起不易找到的bug,因为它们的构造和析构的顺序是不固定的。


对象如果存储在内存中的静态区(包括全局变量、静态变量、静态类成员变量和函数内部静态变量),必须是简单旧式数据(Plain Old Data[vi]),即整型、字符型、单精度型或者简单旧式数据类型的指针、数组、结构体。


但是,C++中并未明确规定静态变量的构造函数和析构函数的调用顺序,甚至每次编译生成后的调用顺序可能都是不一样的。这样就可能导致不易发现的bug。因此,不但禁止将类类型作为全局变量,还禁止使用函数返回值对静态简单旧式数据变量进行初始化,除非这个函数(如getenv(),或getpid())本身并不依赖于其他的全局变量。


按规定,析构函数的调用顺序与构造函数的调用顺序应当是正好相反的。因为构造函数的调用顺序是不确定的,所以析构函数的调用顺序也是不确定的。例如:在程序运行过程中,一个静态变量已经被销毁了,但是程序依然在运行,也许正好有另外一个线程试图去使用这个变量,这当然会失败。抑或是在调用某“string”类型静态变量析构函数后,才去调用另一个变量的析构函数,而这个变量内包含有对此string的引用。


因此,规定静态变量只能是简单旧式数据类型。这条规则明确禁止把vector (可以使用C语言中的数组代替)和string (可以使用const char [])声明为静态。


如果需要使用静态的或全局的类类型变量,那么请在main函数或pthread_once函数中初始化为一个指针(这个指针将不会被释放)。注意,这个指针必须是裸指针(raw pointer[vii]),而不能是智能指针(smart pointer),因为智能指针的析构函数也有上文讨论过的调用顺序问题。








[i] 译者注:unnamed namespace原意为未命名的命名空间。在C++中,命名空间分为命名的命名空间和未命名的命名空间。实际上,未命名的命名空间在编译时,编译器也会为它分配一个自动生成的唯一名称,以与其他的命名空间相区分。故这里译为匿名命名空间(不是没有名,而是人看不到它的名字)。与此对应,named namespace译为定名命名空间。




[ii] 译者注:这里读者需要对C++的历史有一些了解。C++是由C语言进化来的。在C语言中,所有的方法、函数以及变量等都是全局作用域命名的。但是随着软件工程规模的扩大,人们发现这样很不利于管理(很容易产生同名冲突)。于是有人提出了类的概念(当然,这只是类概念产生的众多原因中的一个)。这样每个类中有自己的方法、函数和变量,与其他类中(以及全局)的相应内容相互区分。故,类与命名空间的共同点就是对方法、函数和变量等名称的隔离(当然,命名空间还可以用来隔离类名)。




[iii] 译者注:One Definition Rule (缩写:ODR)定义为:


1. 任何编译单元中,模板、类型、函数、对象等最多都只能有一次定义。当然,它们中有的可以在多处声明。定义就是构造一个实例。


2. 在整个程序中,一个对象或内联函数,只能有一处定义。如果使用了某对象或函数,那么这个对象或函数就必须得有唯一的定义。可以声明一个对象或函数,但却不使用它,这时不需要对它进行定义。任何情况下,都不能有多于一次的定义。


3. 有些内容,如类型、模板、外部内联函数(extern关键字修饰的内联函数),可以在多个编译单元中定义。对于一个给定的实体,所有的定义必须一致。不同编译单元中,同名且同类型(同参数)的非外部对象和函数是不同的实体。




[iv] 译者注:gflags是一个库文件包,主要用来处理命令行中的标识。从这点上讲,它可以替代getopt()函数。不同的是,它更具灵活性。不仅支持C++的类型(如string),还可以在使用标识的源代码文件中定义新的标识。




[v] 译者注:undefined behavior是指在C/C++中,为了提高实现编译器的灵活性,对某些特定行为,在语言的标准中并没有规定他们的具体表现。所以,不同的编译器中,这些行为的表现可能是不同的。也是下面说不可移植的原因。




[vi] 译者注:Plain Old Data,是未经包装,且不含任何面向对象特性的变量类型构成的数据结构。




[vii] 译者注:raw pointer,即普通指针,与智能指针概念相对应。我们知识,智能指针是经过封装的指针,所以普通指针被译为裸指针。



你可能感兴趣的:(Felomeng翻译:Google C++ 编程规范——作用域收藏 function change_alt1(btn,style){var btn=document.getElementById(btn);btn.style.display = style;}此文于2011-03-24被推荐到CSDN首页如何被推荐?)