Google C++编程风格指南(三)之作用域的相关规范

1.名字空间(Namespaces)

C++在C的基础上引入了名字空间机制,使C中作用域的级别从原有的文件域(全局作用域)和局部域(函数作用域和代码块作用域)中间增加了名字空间域和类域。

名字空间是ANSIC++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。

优点:命名空间提供了(可嵌套)命名轴线(name axis,注:将命名分割在丌同命名空间内),当然,类也提供了(可嵌套)的命名轴线(注:将命名分割在丌同类的作用域内)。

缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。在头文件中使用不具名的空间(匿名名字空间)容易违背C++的唯一定义原则(One Definition Rule (ODR))。

使用名字空间应该坚持以下几点规范:

(1)推荐和提倡使用匿名名字空间

// .cpp文件中 
namespace { 
// 命名空间的内容无需缩进
enum { UNUSED, EOF, ERROR }; // 经常使用的符号 
bool AtEof() { return pos_ == EOF; } // 使用本命名空间内的符号EOF 
} // namespace 

不具名名字空间结束时用注释// namespace标识。

使用匿名名字空间的作用主要是将匿名名字空间中的成员的作用域限制在源文件中,其作用域static关键字类似,但是其static关键字不同的是:包含在匿名名字空间中的成员(变量或者函数)具有外部连接特性,而用static修饰的变量或者函数具有内部连接特性,不能用来实例化模板的非类型参数。

参考如下代码:

#include <iostream>
using namespace std;

template <char*p> class Example{
public:
    void display(){
        cout<<*p<<endl;
    }
};

static  char c='a';
int main(int argc,char* argv[])
{

    Example<&c> a; //编译出错
    a.display();
}

此程序无法通过编译,因为静态变量c不具有外部连接特性,因此不是真正的“全局”变量。而类模板的非类型参数要求是编译时常量表达式,或者是指针类型的参数要求指针指向的对象具有外部连接性。

同样是上面的这个程序,将char c=’a’;至于匿名空间进行定义,即可通过编译并运行。读者可自行考证。

(2)最好不要使用using指示符来引用名字空间 使用using指示符实际上就是取消了名字空间的保护作用,增加了命名冲突的概率。考察如下程序:

#include <iostream>
using std::cout;
using std::endl;

namespace FOO{
    int a=1;
}

int a=2;

int main(){
    using namespace  FOO;//引入了与a同名的变量
    cout<<"a:"<<a<<endl; //出现二义性
    return 0;
}

以上程序编译不通过,原因是使用using导入名字空间后,引入了名字空间中所有的成员,间接的取消了名字空间的保护作用,增加了同名标识符的命名冲突。如果要访问名字空间FOO中的变量a的话,真确的用法应该是使用作用域运算符::来指明a所在的作用域,即cout<<FOO::a<<endl;

(3)尽量不要使用全局函数 应该使用命名空间中的非成员函数和类的静态成员函数。这样做的原因是在某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数置于命名空间中可避免对全尿作用域的污染。

有时,不把函数限定在类的实体中是有益的,甚至需要这么做,要么作为静态成员,要么作为非成员函数。非成员函数不应依赖于外部发量,并尽量置亍某个命名空间中。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用名字空间。

定义于同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的合和连接依赖;静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置亍独立库的命名空间中。

如果你确实需要定义非成员函数,又只是在.cpp文件中使用它,可使用不具名名字空间或static关联(如static int Foo() {…})限定其作用域。


2.嵌套类(Nested Class)

在一个类体中定义的类叫作嵌套类,也叫成员类(member class)。拥有嵌套类的类叫外围类,有些地方也叫被嵌套类。

class Foo { 
private: 
    //Bar是嵌套在Foo中的成员类 
    class Bar { 
        ... 
    }; 
};

优点:当嵌套(成员)类叧在被嵌套类(enclosing class)中使用很有用,将其置亍被嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在.cpp文件中定义嵌套类,避免在被嵌套类中包含嵌套类的定义,因为嵌套类的定义通常叧不实现相关。

缺点:叧能在被嵌套类的定义中才能前置声明嵌套类。因此,仸何使用Foo::Bar*挃针的头文件必须包吨整个Foo的声明。

规范:不要将嵌套类定义为public,除非它们是接口的一部分,比如,某方法使用了返个类的一系列选项。


3.局部变量(Local Variables)

(1)将局部变量尽可能置于最小作用域内,在定义时将其显示初始化 C++允许在函数的任何位置声明和定义变量。我们提倡在尽可能小的作用域中定义变量,离第一次使用越近越好。返使得代码易于阅读,易于定位变量的定义位置、变量类型和初始值。特别是,在定义变量时应显示的初始化。

int i; 
i = f(); // 坏——初始化和声明分离 

int i = g(); // 好——初始化时声明

(2)构造数据类型的变量尽可能放在循环体外定义 如果发量是一个对象,每次进入作用域都要调用其构造函数,每次退出作用域都要调用其析构函数。

 // 低效的实现
 for (int i = 0; i < 1000000; ++i) {
     Foo f; // 构造函数和析构函数分别调用1000000次! 
     f.DoSomething(i);
}

似发量放到循环作用域外面声明要高效的多:

Foo f; //构造函数和析极函数只调用1次
for(int i = 0; i < 1000000; ++i){
    f.DoSomething(i); 
}

4.全局变量(Global Variables)

(1)尽量不要定义构造类型的全局变量 构造类型的全局变量,如类对象的构造函数、析构函数以及初始化操作的调用顺序只是被部分规定,每次生成有可能会有发化,从而导致难以发现的bugs。

因此,应禁止使用class类型的全局变量(包括STL的string, vector等等),因为它们的初始化顺序有可能导致构造出现问题。内建类型和由内建类型极成的没有极造函数的结构体可以使用,如果你一定要使用class类型的全局变量,请使用单件模式(singleton pattern)。

(2)对于全局的字符串常量,使用C风格的字符串,而不要使用STL的字符串

const char kFrogSays[] = "ribbet";

虽然允许在全局作用域中使用全局发量,使用时务必三思。大多数全局变量应该是类的静态数据成员,或者当其只在.cpp文件中使用时,将其定义到不具名名字空间中,或者使用静态关联以限制变量的作用域。

记住,静态成员变量视为作用域限制在类域的全局变量,所以,也不能是class类型!

5.小结

  1. .cpp源文件中的不具名名字空间可避免命名冲突、限定作用域,避免直接使用using指示符污染命名空间;
  2. 嵌套类符合局部使用原则,只是不能在其他头文件中前置定义,尽量不要设为public;
  3. 尽量不用全局函数和全局变量,考虑作用域和命名空间限制,尽量单独形成编译单元;
  4. 多线程中的全局变量(含静态成员变量)不要使用class类型(含STL容器),避免不明确行为导致的bugs。

作用域的使用,除了考虑名称污染、可诺性之外,主要是为降低耦合度,提高编译、执行效率。


参考文献

[1]http://blog.csdn.net/k346k346/article/details/48089725 [2]http://blog.csdn.net/k346k346/article/details/48048965 [3]百度文库.Google C++编码规范中文版.http://wenku.baidu.com/link?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿凯的Excel

Python读书笔记5(字符串相关应用)

上期分享了Python相关的字符串应用,重点分享了转义字符。今天和大家分享和字符串相关的函数和应用。 一、字符串的合并! ? Python用“+”号可以连接...

3655
来自专栏开发之途

Java集合框架源码解析之数组与链表

1906
来自专栏前端侠2.0

大白话讲解Promise(一)一文 的学习+新领悟

1、Promise是一个构造函数,自己身上有all、reject、resolve、then、catch。。。。。

2012
来自专栏前端知识分享

第205天:面向对象知识点总结

JSON全称为JavaScript对象简单表示法(JavaScript Object Notation)

923
来自专栏博岩Java大讲堂

Java虚拟机--对象的建立你的对象如何创建?

3176
来自专栏AI研习社

正则表达式教程:实例速查

正则表达式(regex 或 regexp)在文本信息提取方面是非常有用的工具,通过查询一个或多个特定搜索模式的匹配实现(例如,特定的ASCII或unicode字...

983
来自专栏iOS122-移动混合开发研究院

【读书笔记】The Swift Programming Language (Swift 4.0.3)

素材:Language Guide 初次接触 Swift,建议先看下 A Swift Tour,否则思维转换会很费力,容易卡死或钻牛角尖。 同样是每一章只总结3...

29210
来自专栏个人随笔

析构函数(C#)

 析构函数又称终结器,用于析构类的实例。 定义   析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系...

3427
来自专栏Python爬虫实战

Python指南:高级程序设计之过程型程序设计进阶

Python 中,函数本身是一种对象,函数名就是对函数的对象引用。如果我们写一个函数名,其后面没有小括号,Python 会知道我们是将其当做对象引用。

1124
来自专栏一枝花算不算浪漫

[Java面试二]Java基础知识精华部分.

4059

扫码关注云+社区

领取腾讯云代金券