名字查詢
C++ 有一個原則,就是識別符號要先宣告,再使用。每一個識別符號在使用的時候,都要找到它對應的宣告。這個過程,就是名字查詢。
在程式的不同結構中,名字查詢的規則也不盡相同。下面簡述如下。
Unqualified name lookup
簡單地說,就是單獨一個識別符號的查詢。
名字空間中對識別符號的使用
名字空間中的使用的識別符號需要在使用前,在當前或包含它的名字空間中被宣告。
名字空間中定義的函式中
這包括函式名之後出現的任何符號,包括參數列中出現的型別、預設引數等。
其宣告需要出現在使用之前,依次查詢當前塊(block),包含當前塊的塊,函式屬於的 namespace,該 namespace 的父 namespace。
namespace A {
namespace N {
void f();
}
}
void A::N::f() {
i = 5;
// i 的宣告會在以下地方被查詢:
// 1) A::N::f 中,i 的只用之前
// 2) namespace N
// 3) namespace A
// 4) 全域性名字空間,A::N::f 的定義之前
}
類定義中的名字
類定義中的名字按其是否在一個 complete-class context ,查詢跪在不同。
Complete-class context 是指:函式體、函式的預設引數、noexcept-specifier,成員初始化
在 complete-class context 外,宣告要在使用之前,查詢範圍包括:當前類、基類的成員、包含當前類定義的類(如有)或其基類成員、包含當前類的函式(如有)、包含當前類的namespace。
namespace M {
class B { };
}
namespace N {
class Y : public M::B {
class X {
int a[i];
};
};
}
// 會在以下位置查詢 i 的宣告:
// 1) class N::Y::X 中,i 使用之前
// 2) class N::Y 中, N::Y::X 定義之前
// 3) N::Y’s 的基類 M::B
// 4) 名字空間 N, N::Y 定義之前
// 5) 全域性名字空間, N 定義之前
在 complete-class context 中,當查詢類成員時,並不要求成員宣告在識別符號使用之前,查詢範圍包括:當前塊或包含的塊(如有)、當前類成員或基類的成員、包含當前類定義的類(如有)的成員或其基類成員、包含當前類的函式(如有)、包含當前類的namespace。
class B { };
namespace M {
namespace N {
class X : public B {
void f();
};
}
}
void M::N::X::f() {
i = 16;
}
// 會在以下位置查詢 i 的宣告:
// 1) M::N::X::f, i 的使用之前
// 2) class M::N::X
// 3) M::N::X 的基類 B
// 4) namespace M::N
// 5) namespace M
// 6) global scope, M::N::X::f 定義之前
friend
友元函式如果定義在類中,那麼其定義的名字查詢規則與類成員一致。
如果友元函式時類成員,那麼識別符號將首先在該函式所在的類中查詢,除非是用於 declarator-id 中的 template-argument 的識別符號。
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); // parameter type is A::AT
friend void A::f2(BT); // parameter type is B::BT
friend void A::f3<AT>(); // template argument is B::AT
};
預設引數、mem-initializer
在預設引數、mem-initializer 的 expression 中,函式引數是可見的,並且會覆蓋外層同名宣告。
列舉
列舉定義中,已定義的 enumerator 在後續定義中可見
static member
類的靜態成員定義中,名字查詢規則同類成員函式
名字空間的資料成員
如果名字空間的資料成員的定義出現在名字空間之外,其定義中的識別符號查詢方式按照其定義在其名字空間中時的規則進行。
namespace N {
int i = 4;
extern int j;
}
int i = 2;
int N::j = i; // N::j == 4
Argument-dependet name lookup
此規則用於函式呼叫。
typedef int f;
namespace N {
struct A {
friend void f(A &);
operator int();
void g(A a) {
int i = f(a); // f is the typedef, not the friend function: equivalent to int(a)
}
};
}
只有在函式是 unqualified-id 的時候會使用該查詢方式。
namespace N {
struct S { };
void f(S);
}
void g() {
N::S s;
f(s); // OK: calls N::f
(f)(s); // error: N::f not considered; parentheses prevent argument-dependent lookup
}
當通常的 unqualified name lookup 可以找到
- 一個類成員
- 一個塊級函式宣告(除去 using-declaration)
- 非函式或函式模板的宣告時
時,Argument-dependent name lookup 不生效。否則,查詢的結果將包含普通查詢結果與 Argument-dependent name lookup 結果的並集。
Argument-dependent name lookup 實際是在引數型別所在的名字空間中查詢,並且:
- 忽略 using-directive
- 非函式與函式模板的宣告將被忽略
- 定義在類中的有元函式可以被找到
Qualified name lookup
形如 A::B::C
形式,對 B
, C
的查詢都是 qualified name lookup。
以 ::
開始的在全域性名字空間中查詢。
如果 ::
前的部分為一個列舉,其後應為該列舉中的 enumerator 。
...
type-name ::
~
type-name 中(兩個 type-name 不一定一致),第二個 type-name 與 第一個 type-name 在相同的範圍內查詢。
當 ::
前不是列舉時,它可能是一個類,或名字空間。
類成員
當 ::
前是一個類時,在類成員及基類中查詢。(注意,是基類,不是基類的成員。)但是有如下幾個例外:
- 對解構函式查詢(見上)
- conversion-function-id 中的 conversion-type-id 的查詢同類成員訪問
- template-id 中的 template-argument 在整個 postfix-expression 的上下文中查詢
- using-declaraion 中的名字,可以找到當前被隱藏類或enumeration
名字空間成員
對名字空間成員的查詢,會首先名字空間本身的成員。只有當此查詢無結果時,才會去查詢透過 using-directive 引入的成員。
Elaborated type specifiers
形如 class A
, struct B::C
等。
對於 unqualifed-id ,按照 unqualified name lookup 規則查詢,但是忽略一切非型別的宣告。
對 class
, struct
, union
,如果找不到則可能宣告一個新的類。
如果其為 qualified-id ,那麼使用 Qualified name lookup 規則查詢,同時忽略一切非型別的名字。此時,即使找不到也不會宣告新的型別。
Class member access
a.b
, p->c
如果 id-expression
是一個 unqualified-id,那麼在物件類中進行查詢。
a.B::b
形式中,B
將首先在 a 的類中查詢。如找不到,則會在整個 postfix-expression 上下文中查詢。
對 simple-template-id 中的 template-arguments 在整個 postfix-expression 上下文中查詢。
在 conversion-function-id 中,其 conversion-type-id 在物件類中查詢。如找不到,則在整個 postfix-expression 中查詢。所有查詢過程中,型別(或型別模板)以外的名字被忽略。