C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
无限定的名字查找
无限定名,即并非出现在作用域解析运算符 ::
右边的名字,其名字查找按下文所述检查各个作用域,直到找到任何种类的至少一个声明时就停止查找,即不再继续检查别的作用域。(注意:在某些语境中所进行的查找会忽略掉一些声明,例如,对用在 ::
左边的名字的查找将忽略函数、变量和枚举项的声明,而对用作基类说明符的名字的查找则会忽略所有的非类型的声明。)
为了进行无限定的名字查找,来自 using 指令所指名的命名空间中的所有声明,都被当成如同处于同时直接或间接包含这条 using 指令和所指名的命名空间的最内层的外围命名空间之中。
对函数调用运算符左边所使用的名字(等价地也包括表达式中的运算符)所进行的无限定的名字查找,在实参依赖查找中说明。
文件作用域
对于在全局(顶层命名空间)作用域中,在任何函数、类或用户声明的命名空间之外所使用的名字,检查全局作用域中这次名字的使用之前的部分:
int n = 1; // n 的声明 int x = n + 1; // OK:找到了 ::n int z = y - 1; // 错误:查找失败 int y = 2; // y 的声明
命名空间作用域
对于在用户声明的命名空间中,在任何函数或类之外所使用的名字,在这个命名空间中这次名字的使用之前的部分中查找,然后再在包含这个命名空间的命名空间中这个命名空间的声明之前的部分中查找,直到到达全局命名空间。
int n = 1; // 声明 namespace N { int m = 2; namespace Y { int x = n; // OK,查找 ::n int y = m; // OK,查找 ::N::m int z = k; // 错误:查找失败 } int k = 3; }
在命名空间外进行定义
对于命名空间成员变量,在其命名空间之外的定义中所使用的名字,要以在命名空间之内使用的名字相同的方式进行查找:
namespace X { extern int x; // 声明,不是定义 int n = 1; // 找到第一个 }; int n = 2; // 找到第二个 int X::x = n; // 找到了 X::n,设置 X::x 为 1
非成员函数的定义
对于在函数中,无论是其函数体还是作为默认实参的一部分而使用的名字,当这个函数是某个用户声明的或者全局的命名空间的成员时,要在包含这次名字使用的块之中,这次使用之前的部分中查找,然后查找其外围块中这个块开始前的部分,等等,直到到达函数体的块为止。然后再在声明了这个函数的命名空间中,直到使用了这个名字的函数定义(并不是其声明)之前的部分中进行查找,然后查找其外围命名空间,等等。
namespace A { namespace N { void f(); int i=3; // 找到第三个(如果第二个没找到) } int i=4; // 找到第四个(如果第三个没找到) } int i=5; // 找到第五个(如果第四个没找到) void A::N::f() { int i = 2; // 找到第二个(如果第一个没找到) while(true) { int i = 1; // 找到第一个:查找结束 std::cout << i; } } // int i; // 找不到这个 namespace A { namespace N { // int i; // 找不到这个 } }
类的定义
对于在类的定义中所使用的名字(包括基类说明符和嵌套类定义),当出现于成员函数体、成员函数的默认实参、成员函数的异常规定、默认成员初始化器(其中该成员可能属于定义在外围类体内的嵌套类)以外的任何位置时,要在下列作用域中查找:
namespace M { // const int i = 1; // 找不到这个 class B { // static const int i = 3; // 找到了第三个(但之后会被访问检查所拒绝) }; } // const int i = 5; // 找到了第五个 namespace N { // const int i = 4; // 找到了第四个 class Y : public M::B { // static const int i = 2; // 找到了第二个 class X { // static const int i = 1; // 找到了第一个 int a[i]; // i 的使用 // static const int i = 1; // 找不到这个 }; // static const int i = 2; // 找不到这个 }; // const int i = 4; // 找不到这个 } // const int i = 5; // 找不到这个
注入类名
对于在类或类模板或其派生类或类模板中所使用的这个类或类模板的名字,无限定名字查找将会找到当前进行定义的类,如同其名字是由成员声明(以公开成员访问)的方式所引入的一样。更多细节见注入类名。
成员函数的定义
对于在成员函数体、成员函数的默认实参、成员函数的异常说明或默认成员初始化器中所使用的名字,进行查找的各个作用域和类的定义中的相同,但要考虑这个类的整个作用域,而不仅是使用了这个名字的声明之前的部分。对于嵌套类来说,其外围类的整个类体都要进行查找。
class B { // int i; // 找到第三个 }; namespace M { // int i; // 找到第五个 namespace N { // int i; // 找到第四个 class X : public B { // int i; // 找到第二个 void f(); // int i; // 也找到第二个 }; // int i; // 找到第四个 } } // int i; // 找到第六个 void M::N::X::f() { // int i; // 找到第一个 i = 16; // int i; // 找不到这个 } namespace M { namespace N { // int i; // 找不到这个 } }
- 无论哪种方式,当检查派生类的基类时,需要遵守下列规则,它们有时被称为虚继承中的优先性:
当子对象 A 是子对象 B 的基类子对象时,子对象 B 中找到的成员的名字将隐藏掉子对象 A 中相同的成员名。(但要注意这并不会隐藏继承晶格中并非 B 的基类的任何其他的 A 的非虚副本。这条规则仅当虚继承时才有效。)由 using 声明所引入的名字,被当成是包含这个声明的类中的名字来处理。检查了各个基类之后,其结果集合必须要么包含来自同一个类型的子对象的静态成员的声明,要么包含来自同一个子对象的非静态成员的声明。 |
(C++11 前) |
构造一个查找集合,它由一些声明和在其中找到了这些声明的子对象所构成。将 using 声明替换为它们所代表的成员,将类型的声明(包括注入类名)替换为它们所代表的类型。若 C 为在其作用域中使用了这个名字的类,则首先检查 C 。若 C 中的声明列表为空,则对其各个直接基类 Bi 各自构造其查找集合(当 Bi 自身也有基类时,递归地应用这条规则)。构造完成后,将各直接基类的查找集合根据以下规则合并为 C 的查找集合:
|
(C++11 起) |
struct X { void f(); }; struct B1: virtual X { void f(); }; struct B2: virtual X {}; struct D : B1, B2 { void foo() { X::f(); // OK,调用了 X::f(有限定查找) f(); // OK,调用了 B1::f(无限定查找) // C++98 规则:B1::f 隐藏 X::f,因此即便从 D 通过 B2 可以访问到 X::f, // 它也不能从 D 中的名字查找所找到。 // C++11 规则:在 D 中对 f 的查找集合并未找到任何东西,继续处理其基类 // 在 B1 中对 f 的查找集合找到了 B1::f,并且完成查找 // 合并时替换了空集,此时在 C 中 对 f 的查找集合包含 B1 中的 B1::f // 在 B2 中对 f 的查找集合并未找到任何东西,继续处理其基类 // 在 X 中对 f 的查找找到了 X::f // 合并时替换了空集,此时在 B2 中对 f 的查找集合包含 X 中的 X::f // 当向 C 中合并时发现在 B2 的查找集合中的每个子对象(X)都是 // 已经合并的各个子对象(B1)的基类,因此 B2 的集合被丢弃 // C 剩下来的就是在 B1 中所找到的 B1::f // (如果使用 struct D : B2, B1,则最后的合并将会*替换掉* // C 此时已经合并的 X 中的 X::f,因为已经加入到 C 中的每个子对象(就是 X) // 都是新集合(B1)中的至少一个子对象的基类, // 其最终结果是一样的:C 的查找集合只包含在 B1 中找到的 B1::f) } };
- 如果无限定的名字查找找到了
B
的静态成员,B
的嵌套类型,在B
中声明的枚举项,则即便在所检查的类的继承树中有多个B
类型的非虚基类子对象,它也是无歧义的:
struct V { int v; }; struct A { int a; static int s; enum { e }; }; struct B : A, virtual V { }; struct C : A, virtual V { }; struct D : B, C { }; void f(D& pd) { ++pd.v; // OK:只有一个 v,因为只有一个虚基类子对象 ++pd.s; // OK:只有一个静态的 A::s,即便在 B 和 C 中都找到了它 int i = pd.e; // OK:只有一个枚举符 A::e,即便在 B 和 C 中都找到了它 ++pd.a; // 错误,有歧义:B 中的 A::a 和 C 中的 A::a }
友元函数的定义
对于在授予友元关系的类体之中的友元函数的定义中所使用的名字,无限定的名字查找按照与成员函数相同的方式进行。对于定义于类体之外的友元函数中所使用的名字,无限定的名字查找按照与命名空间中的函数相同的方式进行。
int i = 3; // f1 找到的第三个,f2 找到的第二个 struct X { static const int i = 2; // f1 找到的第二个,f2 找不到这个 friend void f1(int x) { // int i; // 找到第一个 i = x; // 找到并改动 X::i } friend int f2(); // static const int i = 2; // f1 在类作用域中的任何地方找到第二个 }; void f2(int x) { // int i; // 找到第一个 i = x; // 找到并改动 ::i }
友元函数的声明
对于在使来自其他类的成员函数为友元的友元函数声明的声明符中所使用的名字,如果该名字不是任何模板实参的一部分,则无限定的查找首先检查成员函数所在类的整个作用域。如果未能在这个作用域中找到(或者如果这个名字是模板实参的一部分),则继续以如同对授予友元关系的类的成员函数进行查找的方式继续查找。
// 这个类的成员函数被作为友元 struct A { typedef int AT; void f1(AT); void f2(float); template <class T> void f3(); }; // 这个类授予友元关系 struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // 对 AT 的查找找到的是 A::AT friend void A::f2(BT); // 对 BT 的查找找到的是 B::BT friend void A::f3<AT>(); // 对 AT 的查找找到的是 B::AT };
默认实参
对于在函数声明的默认实参中所使用的名字,或者在构造函数的成员初始化器的 表达式 部分所使用的名字,在检查其外围的块、类或命名空间作用域之前,首先会找到函数形参的名字:
class X { int a, b, i, j; public: const int& r; X(int i): r(a), // 将 X::r 初始化为指代 X::a b(i), // 将 X::b 初始化为形参 i 的值 i(i), // 将 X::i 初始化为形参 i 的值 j(this->i) // 将 X::j 初始化为 X::i 的值 { } } int a; int f(int a, int b = a); // 错误:对 a 的查找找到了形参 a,而不是 ::a // 但在默认实参中不允许使用形参
静态数据成员的定义
对于在静态数据成员的定义中所使用的名字,其查找按照与对成员函数的定义中所使用的名字相同的方式进行。
struct X { static int x; static const int n = 1; // 找到第一个 }; int n = 2; // 找到第二个 int X::x = n; // 找到了 X::n,将 X::x 设置为 1 而不是 2
枚举项的声明
对于在枚举项的声明的初始化器部分中所使用的名字,在无限定的名字查找处理其外围的块、类或命名空间作用域之前,会首先找到同一个枚举中之前所声明的枚举项。
const int RED = 7; enum class color { RED, GREEN = RED+2, // RED 找到了 color::RED ,而不是 ::RED ,因此 GREEN = 2 BLUE = ::RED+4 // 有限定查找找到 ::RED , BLUE = 11 };
函数 try 块的 catch 子句
对于在函数 try 块的 catch 子句中所使用的名字,其查找按照如同对在函数体的最外层块的最开始处使用的名字一样进行(特别是,函数形参是可见的,但这个最外层块中声明的名字则不可见)。
int n = 3; // 找到第三个 int f(int n = 2) // 找到第二个 try { int n = -1; // 找不到这个 } catch(...) { // int n = 1; // 找到第一个 assert(n == 2); // 对 n 的查找找到了函数形参 f throw; }
重载的运算符
对于在表达式中所使用的运算符(比如在 a+b
中使用的 operator+
),其查找规则和对在如 operator+(a,b)
这样的显式函数调用表达式中所使用的运算符是有所不同的:当处理表达式时要分别进行两次查找:对非成员的运算符重载,也对成员运算符重载(对于同时允许两种形式的运算符)。然后按重载解析所述将这两个集合与内建的运算符重载以平等的方式合并到一起。而当使用显式函数调用语法时,则进行常规的无限定名字查找:
struct A {}; void operator+(A, A); // 用户定义的非成员 operator+ struct B { void operator+(B); // 用户定义的成员 operator+ void f (); }; A a; void B::f() // B 的成员函数定义 { operator+(a,a); // 错误:在成员函数中的常规名字查找 // 找到了 B 的作用域中的 operator+ 的声明 // 并于此停下,而不会达到全局作用域 a + a; // OK:成员查找找到了 B::operator+,非成员查找 // 找到了 ::operator+(A,A),重载决议选中了 ::operator+(A,A) }
模板的定义
对于在模板的定义中所使用的非待决名,当检查该模板的定义时将进行无限定的名字查找。在这个位置与声明之间的绑定并不会受到在实例化点可见的声明的影响。而对于在模板定义中所使用的待决名,其查找则推迟到得知其模板实参之时。此时,ADL 将同时在模板的定义语境和在模板的实例化语境中检查可见的具有外部连接的 (C++11 前)函数声明,而非 ADL 的查找则只检查在模板的定义语境中可见的具有外部连接的 (C++11 前)函数声明。(换句话说,在模板定义之后添加新的函数声明,除非通过 ADL 否则仍是不可见的。)如果在 ADL 查找所检查的命名空间中,在某个别的翻译单元中声明了一个具有外部连接的更好的匹配声明,或者如果当同样检查这些翻译单元时其查找会导致歧义,则其行为是未定义的。无论哪种情况,如果某个基类取决于某个模板形参,则无限定名字查找不会检查它的作用域(在定义点和实例化点都不会)。
void f(char); // f 的第一个声明 template<class T> void g(T t) { f(1); // 非待决名:名字查找找到了 ::f(char) 并于此时绑定 f(T(1)); // 待决名:查找推迟 f(t); // 待决名:查找推迟 // dd++; // 非待决名:名字查找未找到声明 } enum E { e }; void f(E); // f 的第二个声明 void f(int); // f 的第三个声明 double dd; void h() { g(e); // 实例化 g<E>,此处 // 对 'f' 的第二次和第三次使用 // 进行查找并找到了 ::f(char)(常规查找)和 ::f(E)(ADL) // 然后重载解析选择了 ::f(E)。 // 这调用了 f(char),然后两次调用 f(E) g(32); // 实例化 g<int>,此处 // 对 'f' 的第二次和第三次使用 // 进行了查找仅找到了 ::f(char) // 然后重载解析选择了 ::f(char) // 这三次调用了 f(char) } typedef double A; template<class T> class B { typedef int A; }; template<class T> struct X : B<T> { A a; // 对 A 的查找找到了 ::A (double),而不是 B<T>::A };
注:关于这条规则的相关缘由和其影响,请参见待决名的查找规则。
模板名
本节未完成 原因:模板名后的 -> 和 . 的双作用域查找 |
模板外的类模板成员
本节未完成 |
引用
- C++11 standard (ISO/IEC 14882:2011):
- 3.4 Name lookup [basic.lookup]
- 10.2 Member name lookup [class.member.lookup]
- 14.6 Name resolution [temp.res]
- C++98 standard (ISO/IEC 14882:1998):
- 3.4 Name lookup [basic.lookup]
- 10.2 Member name lookup [class.member.lookup]
- 14.6 Name resolution [temp.res]