Scope
每个在 C++ 程序中出现的 声明 仅在某些可能不连续的 作用域  内可见。
在作用域内, 非限定名称查找 可用于将名称与其声明关联起来。
| 目录 | 
概述
每个程序都有一个 全局作用域  ,它 包含 整个程序。
       其他所有作用域
       
        S
       
       均由以下方式之一引入:
      
| 
 | (since C++26) | 
       
        S
       
       总是出现在另一个作用域中,该作用域因此
       
        包含
       
       
        S
       
       。
      
程序点处的 enclosing scope 是指包含该点的任何作用域;其中最小的作用域被称为该点的 immediate scope 。
       一个作用域在程序点
       
        P
       
       和作用域
       
        S
       
       (不包含
       
        P
       
       )之间
       
        介入
       
       的条件是:该作用域等于或包含
       
        S
       
       ,但不包含
       
        P
       
       。
      
       任何非
       
        模板参数作用域
       
       的作用域
       
        S
       
       ,其
       
        父作用域
       
       是指包含
       
        S
       
       的最小作用域,且该作用域本身不是模板参数作用域。
      
除非另有说明:
- 声明在其 声明点 处占据直接作用域。
- 声明的目标作用域即其所占据的作用域。
- 声明(重新)引入的任何名称都会在其目标作用域中与该声明绑定。
       一个实体
       
        属于
       
       作用域
       
        S
       
       ,当且仅当
       
        S
       
       是该实体声明的目标作用域。
      
// 全局作用域 作用域S 作用域T // 作用域 S T int x; // ─┐ // 程序点 X // │ { // │ ─┐ { // │ │ ─┐ int y; // │ │ │ // 程序点 Y } // │ │ ─┘ } // ─┘ ─┘
在上述程序中:
- 
        全局作用域、作用域
        S和作用域T包含程序点Y。
- 
        - 
          换言之,这三个作用域在程序点
          Y处均为封闭作用域。
 
- 
          换言之,这三个作用域在程序点
          
- 
        全局作用域包含作用域
        S和T,且作用域S包含作用域T。
- 
        - 
          因此,作用域
          T是所有三者中最小的作用域,这意味着:
 - 
          - 
            作用域
            T是程序点Y处的直接作用域。
- 
            变量
            
             
              y
             
            
            的声明在其所在位置隶属于作用域
            T。
- 
            作用域
            T是变量 y 声明的目标作用域。
- 
            变量
            
             
              y
             
            
            属于作用域
            T。
 
- 
            作用域
            
 - 
          作用域
          S是作用域T的父作用域,全局作用域是作用域S的父作用域。
 
- 
          因此,作用域
          
- 
        作用域
        S在程序点X与作用域T之间介入。
块作用域
每个
- 选择语句 ( if , switch ),
- 迭代语句 ( for , 范围 for (C++11 起) , while , do - while ),
- 异常处理程序 , 或
- 复合语句 (且非异常处理程序的 复合语句 )
引入一个包含该语句或处理程序的 块作用域 。
属于块作用域的变量是 块变量 (也称为局部变量)。
int i = 42; int a[10]; for (int i = 0; i < 10; i++) // 内部的“i”占据由for语句引入的块作用域 a[i] = i; // int j = i; // j = 42
       若声明位于块作用域
       
        S
       
       中,且声明了函数或使用了
       
        
         extern
        
       
       说明符
       
        
         ,则该声明不得附加到
         
          具名模块 
         
        
        
         
          (C++20 起)
         
        
       
       ,其目标作用域为更大的外围作用域(最内层的外围命名空间作用域),但名称仍绑定在直接作用域
       
        S
       
       中。
      
       如果声明
       
        
         (不是
         
          名称无关声明
         
         且
        
        
         
          (C++26 起)
         
        
       
       在块作用域
       
        S
       
       中绑定了名称
      
- 函数体或函数 try 块的 复合语句 ,
| 
 | (since C++11) | 
- 选择或迭代语句的一个子语句,其本身不是选择或迭代语句,或
- 函数 try 块的处理程序
       
        可能与
       
       目标作用域为
       
        S
       
       的父作用域的声明产生潜在冲突,此时程序非良构。
      
if (int x = f()) // 声明变量“x” { // if代码块是if语句的子语句 int x; // 错误:“x”重复声明 } else { // else代码块同样是if语句的子语句 int x; // 错误:“x”重复声明 } void g(int i) { extern int i; // 错误:“i”重复声明 }
函数参数作用域
       每个
       
        形参声明
       
       
        P
       
       都会引入一个包含
       
        P
       
       的
       
        函数形参作用域
       
       。
      
- 如果声明的参数属于 函数声明 的参数列表:
| 
 | (C++11 起) | 
| 
 | (C++17 起) | 
| 
 | (C++20 起) | 
int f(int n) // 参数“n”的声明 { // 引入函数参数作用域 /* ... */ } // 函数参数作用域在此结束
| Lambda作用域
          每个
          
           lambda表达式
          
          都会引入一个
          
           lambda作用域
          
          ,该作用域从
           带有初始化器的 捕获 位于由 E 引入的lambda作用域内。 auto lambda = [x = 1, y]() // 此lambda表达式引入一个lambda作用域, { // 它是捕获“x”的目标作用域 /* ... */ }; // lambda作用域在分号前结束 | (since C++14) | 
命名空间作用域
       每个为命名空间
       
        N
       
       定义的
       
        命名空间定义
       
       都会引入一个
       
        命名空间作用域
       
       
        S
       
       ,该作用域包含所有为
       
        N
       
       定义的命名空间定义中的
       
        声明
       
       。
      
       对于每个非友元重声明或特化,若其目标作用域为
       
        S
       
       或被
       
        S
       
       包含,则以下部分也会被纳入作用域
       
        S
       
       :
      
- 对于 类 (模板)重声明或类模板特化,指其 类头名称 之后的部分。
- 对于 枚举 重声明,指其 枚举头名称 之后的部分。
- 对于任何其他重声明或特化,指 声明符 的 非限定标识符 或 限定标识符 之后的部分。
namespace V // 命名空间“V”的定义 { // 引入命名空间作用域“S” // 作用域“S”的第一部分从这里开始 void f(); // 作用域“S”的第一部分到这里结束 } void V::f() // “f”之后的部分同样属于作用域“S” { void h(); // 声明 V::h } // 作用域“S”的第二部分到这里结束
类作用域
       每个类或类模板
       
        C
       
       的声明都会引入一个
       
        类作用域
       
       
        S
       
       ,该作用域包含
       
        C
       
       的
       
        类定义
       
       中的
       
        成员声明部分
       
       。
      
       对于每个非友元重声明或特化,若其目标作用域为
       
        S
       
       或被
       
        S
       
       包含,则下列部分也会被纳入作用域
       
        S
       
       :
      
- 对于 类 (模板)重声明或类模板特化,指其 类头名称 之后的部分。
- 对于 枚举 重声明,指其 枚举头名称 之后的部分。
- 对于任何其他重声明或特化,指 声明符 的 非限定标识符 或 限定标识符 之后的部分。
class C // 类“C”的定义 { // 引入类作用域“S” // 作用域“S”的第一部分从这里开始 void f(); // 作用域“S”的第一部分到这里结束 } void C::f() // “f”之后的部分同样属于作用域“S” { /* ... */ } // 作用域“S”的第二部分到这里结束
枚举作用域
       每个枚举
       
        E
       
       的声明都会引入一个
       
        枚举作用域
       
       ,该作用域包含
       
        E
       
       的
       
        
         非不透明
        
        
         
          (C++11 起)
         
        
       
       
        枚举声明
       
       中的
       
        枚举项列表
       
       (如果存在)。
      
enum class E // 枚举类型“E”的声明 { // 引入枚举作用域“S” // 作用域“S”从此处开始 e1, e2, e3 // 作用域“S”在此处结束 }
模板参数作用域
每个 模板模板参数 都会引入一个 模板参数作用域 ,该作用域包含整个模板参数列表 以及该模板模板参数的 require 子句 (C++20 起) 。
       每个模板声明
       
        D
       
       都会引入一个
       
        模板参数作用域
       
       
        S
       
       ,该作用域从
       
        D
       
       的模板参数列表起始处延伸至
       
        D
       
       的结尾。任何位于模板参数列表外部且本应属于
       
        S
       
       的声明,实际上会归属于与
       
        D
       
       相同的作用域。
      
只有模板参数属于模板参数作用域,且只有模板参数作用域具有模板参数作用域作为其父级作用域。
// 类模板“X”的声明 // 引入了模板参数作用域“S1” template < // 作用域“S1”从此处开始 template // 模板模板参数“T” // 引入了另一个模板参数作用域“S2” < typename T1 typename T2 > requires std::convertible_from<T1, T2> // 作用域“S2”在此结束 class T, typename U > class X; // 作用域“S1”在分号前结束 namespace N { template <typename T> using A = struct X; // “X”与模板声明位于同一作用域 // 即命名空间“N”的作用域 }
| 合约断言作用域
          每个
          
           合约断言
          
           
          若某个
          
           后置条件断言
          
          具有非
          
           名称独立
          
          的
          
           标识符
          
          ,且该后置条件断言关联的函数
          
           
            func
           
          
          与声明
           
 | (since C++26) | 
声明点
通常而言,名称在其首次声明的 作用域 之后可见,该作用域按以下方式确定。
名称在简单声明中的位置紧随该名称的 声明符 之后,并在其初始化器(如果存在)之前。
int x = 32; // 外层 x 处于作用域中 { int x = x; // 内层 x 在初始化式 (= x) 之前已进入作用域 // 这不会用外层 x 的值 (32) 初始化内层 x, // 而是用其自身的(不确定)值初始化内层 x } std::function<int(int)> f = [&](int n){ return n > 1 ? n * f(n - 1) : n; }; // 函数名 f 在 lambda 表达式内处于作用域中, // 可通过引用正确捕获,从而形成递归函数
const int x = 2; // 外层 x 在作用域内 { int x[x] = {}; // 内层 x 在初始化器 (= {}) 之前进入作用域 // 但在声明符 (x[x]) 之后 // 在声明符中,外层 x 仍在作用域内 // 这里声明了一个包含 2 个 int 的数组 }
类或类模板声明的定位点紧随其 类头 中命名该类的标识符(或命名模板特化的 模板ID )之后。在基类列表中,该类或类模板名称已处于作用域内。
struct S: std::enable_shared_from_this<S> {}; // 冒号处 S 已在作用域内
枚举说明符 enum specifier 或不透明枚举声明 (since C++11) 的位置紧跟在命名该枚举的标识符之后。
enum E : int // E 在冒号处已进入作用域 { A = sizeof(E) };
类型别名或别名模板声明的定位点紧随该别名所指代的类型标识之后。
using T = int; // 外层的 T 在分号处处于作用域内 { using T = T*; // 内层的 T 在分号处处于作用域内, // 外层的 T 在分号前仍处于作用域内 // 等同于 T = int* }
对于 using 声明 中不命名构造函数的声明符,其作用域起始位置紧接在该声明符之后。
template<int N> class Base { protected: static const int next = N + 1; static const int value = N; }; struct Derived: Base<0>, Base<1>, Base<2> { using Base<0>::next, // next 在逗号处进入作用域 Base<next>::value; // Derived::value 的值为 1 };
枚举项的定位点紧随其定义之后(不同于变量定义中位于初始化表达式之前的位置)。
const int x = 12; { enum { x = x + 1, // 枚举项 x 在逗号处进入作用域, // 外部 x 在逗号前处于作用域内, // 枚举项 x 被初始化为 13 y = x + 1 // y 被初始化为 14 }; }
注入类名的位置紧接在其类(或类模板)定义的开头花括号之后。
template<typename T> struct Array // : std::enable_shared_from_this<Array> // 错误:注入类名不在作用域内 : std::enable_shared_from_this< Array<T> > // 正确:模板名称 Array 在作用域内 { // 注入类名 Array 现在在作用域内,如同公共成员名称 Array* p; // 指向 Array<T> 的指针 };
| 函数局部预定义变量 __func__ 的隐式声明位置紧接在函数定义体之前。 | (since C++11) | 
       
      
| 结构化绑定声明 的作用域起始位置紧接在 标识符列表 之后,但禁止结构化绑定的初始化器引用任何正在被声明的名称。 | (since C++17) | 
       
      
| 在 范围- for 循环 的 范围声明 中声明的变量 或结构化绑定 (since C++17) 的作用域起始位置紧接在 范围表达式 之后。 std::vector<int> x; for (auto x : x) // vector x 在右括号前处于作用域内, // auto x 在右括号处进入作用域 { // 此处 auto x 在作用域内 } | (since C++11) | 
模板形参的作用域起始于其完整模板形参(包括可选的默认实参)之后。
typedef unsigned char T; template< class T = T, // 模板参数 T 在逗号处处于作用域内, // typedef 名称 unsigned char 在逗号前处于作用域内 T // 模板参数 T 处于作用域内 N = 0 > struct A { };
| 
          带有
          
           标识符
          
          的
          
           后置条件断言
          
          的定位点紧接在其
           | (since C++26) | 
       
      
| 概念定义 的作用域起始于概念名称之后,但禁止概念定义引用正在声明的概念名称。 | (since C++20) | 
命名 命名空间定义 的作用域起始点位于命名空间名称之后。
| 本节内容不完整 原因:[basic.scope.pdecl] 条款的剩余部分 | 
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 | 
|---|---|---|---|
| CWG 2793 | C++98 | 块作用域中的
         
          
           extern
          
         
         声明可能 与父作用域中的其他声明产生冲突 | 已禁止 | 
参考文献
- C++23 标准 (ISO/IEC 14882:2024):
- 
         - 6.4 作用域 [basic.scope]
 
- C++20 标准 (ISO/IEC 14882:2020):
- 
         - 6.4 作用域 [basic.scope]
 
- C++17 标准 (ISO/IEC 14882:2017):
- 
         - 6.3 作用域 [basic.scope]
 
- C++14 标准 (ISO/IEC 14882:2014):
- 
         - 3.3 作用域 [basic.scope]
 
- C++11 标准 (ISO/IEC 14882:2011):
- 
         - 3.3 作用域 [basic.scope]
 
- C++98 标准 (ISO/IEC 14882:1998):
- 
         - 3.3 声明区域与作用域 [basic.scope]
 
参见
| 
          
           
            C 文档
           
          
          关于
          
           
            作用域
           
          
          |