作用域

名空间

命名空间  

我们鼓励在.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初始化一个指针。这个指针应该是一个原始的指针,而不是智能指针,由于智能指针存在析构顺序的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值