命名空间
我们鼓励在.cpp文件使用匿名命名空间;也可以使用具名命名空间,选择一个基于项目的名称。不要用使用指令。
定义:
命名空间把全局范围细分为不同的具名的作用域,对于避免在全局范围的名称冲突是很有用的。
优点:
命名空间提供了一种分层的命名,除了命名空间之外,类也提供了这种分层的命名方式。
例如,有两个不同的工程,它们都拥有一个全局的类Foo,在编译或者运行时,这些类标识就有可能引发冲突。如果他们把各自的代码放在命名空间中,如Project1::Foo和Project2::Foo,他们就不会出现冲突的情况。
缺点:
命名空间会引起混淆。由于除了类提供的命名层次,还增加了额外的命名层次。在头文件使用不具名的命名空间,容易违反一次性定义原则。
决定:
像下面描述的规则来使用命名空间,终止像注释例子那样使用命名空间如。
匿名的命名空间
1、 在cpp文件允许并鼓励使用匿名命名空间来避免命名冲突。
namespace { // This is in a .ccfile.
// 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
然而,关联到特定类的文件范围的声明,是被当成类的一个类型,静态数据或者静态方法,而不是匿名命名空间。
2、不用在头文件使用匿名命名空间。
命名的命名空间
1、 命名的命名空间包含在#include,全局标识的声明和定义,以及来自其他命名空间的类的前向声明后面 整个源文件。
2、 // In the .h file
3、 namespace mynamespace {
4、
5、 // All declarations are within the namespace scope.
6、 // Notice the lack of indentation.
7、 class MyClass {
8、 public:
9、 ...
10、 void Foo();
11、 };
12、
13、 } // namespace mynamespace
14、 // In the .cc file
15、 namespace mynamespace {
16、
17、 // Definition of functions is within scope of the namespace.
18、 void MyClass::Foo() {
19、 ...
20、 }
21、
22、 } // namespace mynamespace
23、
24、 #include "a.h"
25、
26、 DEFINE_bool(someflag, false, "dummy flag");
27、
28、 class C; // Forward declaration of class C in the global namespace.
29、 namespace a { class A; } // Forward declaration of a::A.
30、
31、 namespace b {
32、
33、 ...code for b... // Code goes against the left margin.
34、
35、 } // namespace b
2、不要在std命名空间里声明任何东西,甚至是来自标准类库的前向声明。在std命名空间声明任何实体会出现未定义行为。要声明名字标准库的实体,需要包含适当的头文件。
3、不要使用using指示来使得命名空间中的标识可用。
// Forbidden -- This pollutes the namespace.
using namespace foo;
4、可以在头文件的类,方法或者cpp文件中使用Using指示。
// OK in .cc files.
// Must be in a function, method or class in .h files.
using ::foo::bar;
5、在cpp文件,包含整个头文件的命名的命名空间,函数和方法中,都允许使用命名空间别名
// 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
注意:.h头文件里的命名空间别名,对于包含它的文件都是可见的。所以公共的和过渡性的头文件都应该避免使用别名,以使得公共API尽可能的小。
嵌套类
即使可以把公共的嵌套类当成接口的一部分,考虑命名空间使声明不具有全局作用域
定义:
可以在类中定义另外的一个类,这样的类也可以称之为成员类。
class Foo {
private:
// Bar is a member class, nested within Foo.
class Bar {
...
};
};
优点:
当嵌套类只被包含它的类使用时这样做是很有用的。把它当成一个成员类,仅仅局限于包含它的类的作用域中,不会污染外部作用域的命名空间。嵌套类可以在外部类里进行前向声明,在cpp文件里进行定义,从而避免了在外部类的声明中包含嵌套类的定义。所以嵌套类的定义一般只与实现相关。
缺点:
嵌套类只能在外部类定义里进行前向声明,因此任何操作Foo::Bar*指针都必须包含Foo类的完整定义。
决定:
不要把嵌套类定义为public,除非它们作为接口的一部分
非成员,静态成员和全局函数
尽量使用命名空间下的非成员函数和静态成员函数,少用全局函数。
优点:
非成员和静态成员函数在某些情况下很有用。把非成员函数放在命名空间里避免污染全局命名空间。
缺点:
非成员和静态函数对于新的类来说有时候更有用,尤其是当访问外部资源或者有明显的依赖关系时。
定义:
不把函数的定义绑定到一个类实例有时候是很有用也是有必要的。这种函数可以是静态成员函数或者非成员函数。非成员函数不能依赖于外部变量,而且应该存在于一个命名空间中。如果创建的类仅仅是把不共享静态数据的静态成员函数聚合,那应该用命名空间替代类。定义在同一编译单元的函数,当被其他编译单元直接调用时会引入不必要的编译和链接依赖性,尤其静态成员函数最容易受此影响。可以考虑创建新的类或者把函数置于独立库的命名空间中。
如果必须定义一个非成员函数,那么只能放在cpp文件,而且用匿名命名空间或者静态链接限制它的作用域
局部变量
把一个函数的变量尽量放在有限的作用域中,而且在声明时进行初始化。
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循环重用i,同样的声明也适用于if或者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);
}
静态和全局变量
Class类型的静态或者全局变量是被禁止的,由于不确定的构造函数和析构函数的调用会使得很难发现bug。如果他们是常量表达式,这样的变量是允许的,它们没有动态的初始化和析构。
静态存储的对象,包括全局变量,静态变量 ,静态类成员变量以及函数静态变量,必须是简单数据类型,例如int,float,char,或者指针,以及简单数据类型的数组和结构。构造函数和静态变量的初始化顺序是部分规定的,每次生成都会有不同的变化,会引起很难发现的bug。因此禁止使用全局的class变量。不允许使用函数的返回值初始化静态的简单数据类型变量,出发函数本身不依赖任何全局变量。
同样的,析构函数的调用顺序与构造函数的调用顺序相反,因为构造函数的调用顺序不确定,所以析构函数的调用顺序也一样是不确定的。例如,程序在结束时,一个静态变量可能被销毁,但是代码还在允许,在别的线程可能试图访问这个变量,就会失败。或者静态string变量的析构函数可能被调用,由于别的string对该变量的引用。
所以我们只允许简单数据类型的静态变量,这个规则也不允许vector和string。
如果确实需要class类型的静态变量或者全局变量,考虑通过main函数或者pthread_once初始化一个指针。这个指针应该是一个原始的指针,而不是智能指针,由于智能指针存在析构顺序的问题。