现在的位置: 首页 > 综合 > 正文

Google C++ Style Guide翻译版第二辑——作用域(Scope)

2013年10月22日 ⁄ 综合 ⁄ 共 3853字 ⁄ 字号 评论关闭

2.作用域(Scoping)

2.1  名称空间(Namespaces)

.cc文件中,通常鼓励匿名名称空间。当需要命名一个名称空间时,可以基于项目或者它的路径。不要使用using指令。

定义:名称空间将全局作用域分成独立且命名的子作用域,因此对于防止命名冲突很有帮助。

利:名称空间提供了一条补充由类提供的分层命名的轴线。

举个例子,如果两个不同的项目有一个同名的全局类Foo,这些符号在编译或者运行时就有可能冲突。如果把它们的代码放在各自项目的名称空间中,project1:Fooproject2::Foo就是不同的符号,也不会发生冲突了。

弊:名称空间可能引起混乱,因为它提供了额外的分层命名轴线以补充由类提供的另一条。

在头文件中使用匿名名称空间很容易违反C++的一次定义规则(One
Definition Rule, ODR)

结论:

请按下面的规则使用名称空间:

l  匿名名称空间:

1.        
C++中,匿名名称空间是允许甚至是被鼓励的:

.cc文件中,为防止运行时命名冲突:

namespace{                 // 这通常包含在一个.cc文件中

 // 名称空间的内容不应该缩进

enum{kUnused,kEOF,kError}
//
常见语句

bool AtEof(){

return
pos == kEOF;

}//使用我们的名称空间

EOF.

}// namespace

然尔,与特定类相关的文件作用域声明可能在类中被作为类型、静态数据成员或者静态成员函数而不是在匿名名称空间中定义。通常使用注释// namespace来结束一个匿名空间的定义。

2.        
不要在头文件中使用匿名名称空间。

l 
命名名称空间:

命名名称空间的使用规则如下:

1.        
名称空间应该包围#include后的所有代码,包括gflags(一种调试工具,由微软发布,用于检测内存泄漏)定义、声明和来自其他名称空间的前置类声明。

 // 位于头文件

 namespace mynamespace{

 // 所有的声明都应该在名称空间作用域内

 // 注意这里没有缩进

 class MyClass{

  
public:

  
...

  
void Foo();

 };

 }// namespace mynamespace

 // 位于.cc文件

 Namespace mynamespace{

 // 所有函数的定义位于名称空间的作用域内

 void MyClass::Foo(){

  
...

 }

 }// namespace mynamespace

通常.cc文件可能包括更复杂的代码,比如从其他名称空间引入的类引用。

#include “a.h”

DEFINE bool(someflag, false,”dummy
flag”);

class C;  // C的全局前置声明

namespace b{

 

... code for b ...    // 代码应该左对齐

 

}// namespace b

2.        
不要在std名称空间中声明任何东西,甚至是标准库类的前置声明。在std名称空间中声明名称是未确定行为或者不可移植的。要从标准库中声明名称,包含相应的头文件即可。

3.        
不可以使用using指令(using-directive)将一个名称空间中的所有名称引入。

// 禁止-这有可能引起名称空间冲突

using namespace foo;

4.        
在源文件、头文件中的函数、方法或者类中可以使用using声明(using-declaration)

// 在源文件中允许

// 但在头文件中必须在函数、方法或者类中

using ::foo::bar;

5.        
名称空间别名可在源文件中、头文件的全局名称空间中,或者函数和方法中任意使用。

// 源文件中常用名称的受限访问

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

// 头文件中常用名称的受限访问

Namespace librarian{

// 下面的别名在所有包含此头文件的都可使用(限于librarian

// 名称空间)

// 因此,别名应该与项目保持一致性

namespace pd_s =
::pipeline_diagnostics::sidetable;

 

inline void my_inline_function(){

 
//
位于函数或者方法内的名称空间别名

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

 
...

}

}// namespace librarian

注意:头文件中的别名在所有包含它的文件中都可见,所以公共头文件(其他项目可以使用)和那些间接被它们包含的头文件,应该避免定义别名,毕竟,通常的目标是尽量保持公共API简小。

2.2  类嵌套(Nested Classes)

尽管你可以使用公共的嵌套类来提供接口,但请尽量使用名称空间来保持声明的局部性。

定义:类可以在其内部定义另一个类(常称为成员类(Member Class))。

class Foo{

 
private:

   
// Bar
是一个成员类,嵌套定义于Foo

class Bar{

 
...

}

}

利:当嵌套类仅被外部类使用时,这种定义方法很有用,这样可以避免在外部定义类引起的作用域混乱。可以前置声明嵌套类而将其实现放在源文件中来避免其定义出现在外部类的声明中,毕竟嵌套类的定义只与其实现相关。

弊:嵌套类仅可被前置声明于外部类内部。这样,任何操纵Foo::Bar*指针的头文件都必须包含Foo类的全部声明。

结论:除非嵌套类是接口的一部分(比如一个包括若干供选择方法的类),否则不要将其定义为公共成员。

2.3  外部函数、静态成员函数和全局函数(Nonmember,
Static Member, and Global Functions)

尽量使用名称空间内的外部函数或者静态成员函数,尽量少使用甚至不使用全局函数。

利:一些情况下,外部和静态成员函数很有用。将一个外部函数放在一个名称空间内可以防止全局名称空间混乱。

弊:外部函数和静态成员函数作为一个新类的成员可能更好,尤其是当它们访问外部资源或者有很大的相关性时。

结论:

有时,定义一个与对象无关的函数(即外部函数或者静态成员函数)是很有用甚至必要的。外部函数不应该依赖全局变量,而且应该定义在一个名称空间内。与类不同,名称空间在定义函数的同时可以共享共变量,而定义静态成员函数的类则不允许共享其静态数据。

共同为某个类提供一项功能而在同一个编译单元内定义的函数可以导致不必要的耦合和连接时相关性,当其他编译单元直接调用时,静态成员函数尤其如此。这时,可以考虑提取出一个新类,或者将这些函数放在不同库中的不同名称空间内。

如果必须定义一个只在其源文件内使用的外部函数,可以使用匿名名称空间或者静态链接(比如static int Foo(){…})来限制其作用域。

2.4  局部变量(Local Variables)

尽量缩小函数内部变量的作用域并在定义它们时执行初始化。

虽然C++允许在函数内部任意定义变量,但我们建议尽量缩小其作用域,尽量在需要时才定义。这样方便读者找到变量声明并知道其类型及初始化情况。特别地,初始化应该代替声明和赋值。例如:

int i;

i = f();      // 糟糕-初始化和声明分开

int j = g(); // 提倡-声明的同时初始化

注意:GCC正确地实现了for循环(for(int i
= 0; i < 10; i ++)
i的作用域仅限于for循环体内,所以在同一作用域内,你可以重复使用iifwhile语句中的局部变量也一样。比如:

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

警告:如果变量是一个类,它的构造函数将在每次进入作用域时被调用并创建实例,它的析构函数也将在每次退出作用域时被调用。

// 低效的实现

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

 Foo f; // 这个类的构造函数和析构函数将被调用1000000

 f.DoSomething();

}

将一个在循环内使用的变量定义在循环外将更高效:

2.5  静态变量和全局变量(Static and
Global Variables)

类的静态和全局使用是被禁止的:这些变量可能由于不确定的构造和析构次序而引起难于发现的缺陷。

有静态存储期间,包括全局变量、静态变量、静态类成员和静态函数变量的类必须是老式平坦数据类型(POD),包括int, char,float,或者指针,或者数组、结构体。

C++中,类对静态成员的构造和初始化仅有部分规定,甚至每次编译都有可能不同,这很容易导致难以察觉的缺陷。因此,除了不允许定义全局类变量外,我们也不允许使用一个函数来初始化一个全局变量,除非这个函数(比如geteny(),getpid())不依赖任何其他全局变量。

同样,类的析构顺序恰恰和其构造顺序相反。由于构造顺序尚且不确定,何况析构顺序呢。举个例子,一个程序行将结束的时候,一个静态变量已经被销毁,但代码仍在运行(也许是另一个线程)并试图访问它,但失败了。或者一个静态string变量的析构可能在另一个包含有该变量的引用的变量析构中被执行。

因此,只允许仅仅包含POD数据的静态变量。显然vector(代替C数组),或者string(用const char[]实现
)都不行。

如果你确实需要定义一个静态或者全局类变量,考虑从主函数或者pthread_onece()函数初始化一个指针(永远不会被销毁)。注意,这个指针一定是一个普通指针而不是智能指针,因为智能指针的析构将面临析构次序的问题。

抱歉!评论已关闭.