有人問(wèn):
class Entity
{
public:
static void foo() {}
};
int main()
{
Entity::Entity::Entity::Entity::Entity::foo();
}
為什么 最后那行:
Entity::Entity::Entity::Entity::Entity::foo();
能編譯成功?這是什么規(guī)則?
嗯……
Entity::Entity::Entity::Entity::Entity::Entity::foo() 竟然編譯成功?這一切的背后,是人性的扭曲,還是道德的淪喪? 敬請(qǐng)關(guān)注今晚八點(diǎn) CPPTV 12 頻道,讓我們跟隨鏡頭走進(jìn)厚厚的C++標(biāo)準(zhǔn)文檔……
這個(gè)案例,至少牽涉到 C++ 中的 以下 知識(shí)點(diǎn):
- “Unqualified name lookup / 未限定的名字查找”
- “Qualified name lookup / 有限定的名字查找”
- “Injected-class-name/ ‘注入式’類名稱 ”以及 特別關(guān)注其中的 “Injected-class-name and constructors / ‘注入式’類名稱 和 構(gòu)造函數(shù) 的關(guān)系 ”。
- “ Elaborated type specifiers / 詳細(xì)的類型說(shuō)明符 ”
如果被限定的名稱,最終發(fā)現(xiàn)是一個(gè)C++基礎(chǔ)類型 (bool, int, char 等),那還得牽涉出: "pseudo-constructor-name 或 pseudo-destructor-name / 偽構(gòu)造或偽析構(gòu)名字 ” 等等
首先我們從 “Qualified name lookup ” 的一特例說(shuō)起: A::A 表示什么?
如果 A 是一個(gè)類 (或結(jié)構(gòu),或相應(yīng)的別名,以下均只以“類”代表 ),并且在上下文限定中,查找 A 符號(hào)的過(guò)程無(wú)須過(guò)濾掉函數(shù)名稱, 那么, A::A 就只能表示 A 的構(gòu)造函數(shù)的名字。比如:
struct A {};
using T = A::A; // 編譯失敗
int main() {}
問(wèn): using 這行代碼能編譯通過(guò)嗎?
答:不行的,編譯將得到類似 “A::A 是構(gòu)造函數(shù),不是類型”這樣的出錯(cuò)信息。如下圖:
這是一個(gè)特例,如果 A 有一個(gè)基類叫 B,則 A::B 立刻表示 一個(gè)類型(即 B ),如:
struct B {};
struct A : B {};
using T = A::B; // 編譯成功
int main() {}
那要怎么讓 A::A 請(qǐng)示 struct A 這個(gè)類型呢?這就要用上 “Elaborated type specifiers”。方法是加上 typename 或 class 或 struct 之一。只要加上三者之一即可,并不需要和 A 倒底是 struct 還是 class 對(duì)應(yīng)上。因?yàn)榧由线@三者,就是為了很 “elaborated ”地表示:這是一個(gè)類型。
我們就選 “typename”,因?yàn)樗雌饋?lái)如此直觀:“類型名”:
struct A {};
using T = typename A::A; // 編譯成功
int main() {}
這時(shí)候生效的是C++的哪一條規(guī)則呢? 答,“Qualified name lookup / 有限定的名字查找” 。先從操作符 “:: ”說(shuō)起。
“ :: ”被稱為 “ scope resolution operator ” ,用于限定一個(gè)符號(hào)的查找范圍,它的右邊的符號(hào),直觀上,我們會(huì)叫它 是“查找目標(biāo)”,它的左邊,直觀上會(huì)叫它 “查找范圍”。比如 C::F ,很容易推想:編譯器 的查找過(guò)程 就是 在 C 的有效范圍里,查找 符號(hào) F 是什么東東。如果是C是一個(gè)class/struct,那查找范圍還可擴(kuò)大到 它的基類(如果有)。事實(shí)上,C++標(biāo)準(zhǔn)也確實(shí)規(guī)定了:解析 C::F 時(shí),編譯器必須先解析 C ,再解析 F……然而,C++標(biāo)準(zhǔn)又規(guī)定了,在解析 C::F 中的C時(shí),應(yīng)該加某種優(yōu)先級(jí),跳過(guò) “Unqualified name lookup”,以嘗試將 C 解析為 某個(gè)類名 (class\struct\union) 、namespace 或 枚舉名 (本質(zhì)也是類型名)。比如,下面的代碼肯定編譯失?。?/p>
struct a
{
static int i;
};
int a::i;
int main()
{
int a;
a a1; // 編譯失敗。
}
“a a1;” 這行中的 a ,沒有加任何空間限定,所以在查找 它 是什么時(shí),用的是 “Unqualified name lookup / 未限定的名字查找”,其方法就是就近往前找,于是找到 a 是一個(gè) int 變量(而不是一個(gè)類型),自然, “a a1;” 語(yǔ)法錯(cuò)誤。
怎么讓編譯器知道我們希望寫在這里的 a 是一個(gè) 類(或結(jié)構(gòu))呢?直觀的想法當(dāng)然是它加上限定:
struct a
{
static int i;
};
int a::i;
int main()
{
int a;
a::i = 666; // 編譯成功
}
編譯器解析過(guò)程如下:看到 “a::i”中有個(gè) “::”,于是采用 “Qualified name lookup ”,于是此處的 a 不受上面 的 “int a”影響,優(yōu)先 將它當(dāng)成 類類型、namespace、或enum 查找……于是找到 struct a 。而 struct a 里面正好有個(gè)靜態(tài)成員 i,滿足 “a::i = 666”的操作……
注意,這個(gè)名字查找過(guò)程中,“a::i”基本被視為一個(gè)整體。否則,如果按要求,一定要先解析出 :: 的左邊的 a 是什么的話,那由于 它本身 未再有 新的限定 (它的左邊不再有 :: ),那么,它就應(yīng)該被解析為 一個(gè) 整數(shù)變量;然后,整數(shù)變量后面接 “::i”,顯然是錯(cuò)誤的語(yǔ)義,編譯失敗——但實(shí)際情況是,編譯成功了。因?yàn)?,加了?:”后,“Qualified name lookup ”的優(yōu)先級(jí)高于“Unqualified name lookup”了。
不過(guò),這個(gè)“優(yōu)先級(jí)”是有限的。如果兩條或更多 “Qualified name lookup”的限定規(guī)則時(shí),此時(shí)大家都是“有身份/Qualified”的人,誰(shuí)也不比誰(shuí)優(yōu)先,于是編譯器就只能報(bào)錯(cuò)了,比如:
namespace n1 {
struct a
{
static int i;
};
int a::i;
} // namespace n1
int main()
{
int a;
a::i = 666; // 編譯失敗,正確做法: n1::a::i = 666; 或 上面 加 using namespace n1;
}
a::i 仍然使用帶限定的名字查找法,仍然優(yōu)先于 int a 中的 “a”的作用;但它卻找不到合適的 a了:現(xiàn)在 struct a 位于 另一個(gè)“qualified / 有限制的” 的空間范圍內(nèi): n1 。此時(shí),要么 加上 using namespace n1 ,要么明確使用 n1::a::i 。
對(duì)于“Qualified name lookup”, 我們還有個(gè)補(bǔ)充:在 一個(gè)類(假設(shè)類名為 C)的范圍里寫代碼,此時(shí)對(duì)符號(hào)的查找,哪怕不加 “ C::”限定,也是會(huì)在 “Unqualified name lookup” 失敗之后,主動(dòng)加上“C::”作為 “Qualified”,再找一次的。
距離扣題,還有最后那么幾步……上面我們講了規(guī)則是什么什么,但沒有講為什么有這些規(guī)則;所以我們還需要一些規(guī)則必要性解釋及“有某規(guī)則和沒有某規(guī)則”的對(duì)比:
很早很早以前,那時(shí)的C++的class內(nèi),是不會(huì)自動(dòng)采用 “Qualified name lookup”再查找一次的,所以:
/* 曾經(jīng),約30多年前的C++, 這個(gè)類定義會(huì)編譯失敗 */
class Coo
{
char c;
public:
void M()
{
c = 'A'; // 編譯成功, 往前找 c ,發(fā)現(xiàn)它是 char
a = 1; // 編譯失敗,a 是什么?
m(); // 編譯失敗, m 是什么?
T t; // 編譯失敗,T 是什么?
}
private:
typedef int T;
void m();
int a;
};
「純猜測(cè)」 C++之父寫的示例代碼,到現(xiàn)在也常常將 私有成員 放在最前面,我懷疑他并不是為了省寫一次“private”,我懷疑他就是習(xí)慣了之前的查找法。
注意上面的表達(dá),當(dāng)沒有明確寫 “::”時(shí),在類中也仍然優(yōu)先使用“Unqualified name lookup”,所以這才有C++程序員都熟悉的,非?!敖?jīng)典”的某種寫法:
class Coo
{
public:
Coo(int a, int b) : a(a), b(b) {}
private:
int a, b;
};
以其中的 “a(a)”為例,表意是 用括號(hào)中(右邊)的 a 初始化 括號(hào)外(左邊)的 a 。兩個(gè) a 都不帶 “::” 限定,因此都優(yōu)先使用 “Unqualified name lookup”,而后 括號(hào)中的 a 解析成功,括號(hào)外的 a ,因?yàn)橐鳛槌跏蓟哪繕?biāo),所以不可能構(gòu)造函數(shù)的入?yún)⒅械?a ,于是改用 “Coo::a ”進(jìn)行“Qualified name lookup”,這回成功了。
現(xiàn)在來(lái)看問(wèn)題中的 Entity :
class Entity
{
public:
static void foo();
}
首先,如何按 “特例” A::A ,如果A是一個(gè)class/struct/,則A::A 必然用于表示 類A 的構(gòu)造函數(shù)名字這規(guī)定,那么題目中的這個(gè)寫法:
Entity::Entity::Entity::foo();
感覺是不合語(yǔ)法的。因?yàn)橐婚_始的 “Entity::Entity” 就應(yīng)該得到一個(gè) 構(gòu)造函數(shù)的名字,而構(gòu)造函數(shù)名字后面再接“::Entity::foo() ……” 是不合語(yǔ)法的。注意,如果沒有的最后的 ::foo(),兩個(gè)或更多的 Entity:: 相連,仍然在表達(dá) 一個(gè)構(gòu)造函數(shù)。但在語(yǔ)法上,構(gòu)造函數(shù)被不允許被直接調(diào)用(析構(gòu)函數(shù)倒是可以),因此,能正確使用 A::A::A::A::A 這樣的寫法,基本就是在構(gòu)造函數(shù)的定義\實(shí)現(xiàn)的時(shí)候了,比如:
// class Entity 的構(gòu)造函數(shù)實(shí)現(xiàn) (能通過(guò)編譯):
Entity::Entity::Entity::Entity::Entity::Entity::Entity()
{
}
在別的地方這么寫,編譯器仍然會(huì)識(shí)別出這是一個(gè)構(gòu)造函數(shù),但它會(huì)基于其它規(guī)則而報(bào)錯(cuò),比如:
void foo()
{
// 直接調(diào)用 (但可惜構(gòu)造函數(shù)不能直接調(diào)用)
Entity::Entity(); // 報(bào)錯(cuò):哎呀,不能直接調(diào)用 構(gòu)造函數(shù)
}
再如前面使用過(guò)的例子:
// 嘗試取類型別名 (但人家 Entify 此處不是類型名 )
using T = Entity::Entity::Entity; // 報(bào)錯(cuò):哎呀,Entity 是構(gòu)造函數(shù)的名字,不是類型啦
既然一串的 “Entity::Entity”表示的是 構(gòu)造函數(shù)的名字,那么怎么解釋 “Entity::Entity::Entity::foo();”卻通過(guò)了編譯,并且在運(yùn)行期正確地執(zhí)行了靜態(tài)成員函數(shù) foo() 呢?
首先,我們要證明一下,“XXX::Foo”作為靜態(tài)成員函數(shù)的調(diào)用的一方式,前面加的XXX一定是一個(gè)類名,而不是構(gòu)造函數(shù)的名字。
其實(shí)不證明也可以,因?yàn)镃++標(biāo)準(zhǔn)規(guī)范中講解 static member function 的調(diào)用時(shí),明確就說(shuō)那個(gè) XXX 是 類名。但,證明一下也不難——
using T = typename Entity::Entity::Entity; //編譯通過(guò),T 現(xiàn)在就是類名
加了 typename (class, struct)之后,后面的 Entity::Entity::Entity ……就是“ Elaborated type specifiers / 詳細(xì)的類型說(shuō)明符 ”,于是它肯定是個(gè)類型名 (type specifiers),于是 T現(xiàn)在肯定就是一個(gè)類名,事實(shí)上就是 class Entity。
然后我們假借 T 來(lái)調(diào)用 靜態(tài)成員:
T::foo(); // 成功
由此可證:foo() 前面 的“T::”,就是一個(gè)“類型限定”(而不是我們意想天開的構(gòu)造函數(shù)名字)。
有意思(其實(shí)超級(jí)煩人)的事來(lái)了:既然 T 就是 typename Entity::Entity::Entity,而 T::foo(),又能成功編譯、運(yùn)行;那我們?yōu)槭裁匆欢ㄒ€(gè)別名呢?直接這樣寫不行嗎:
typename Entity::Entity::Entity::foo(); // 行嗎?不行!
這樣寫多直觀??!可惜,替換率竟然在此失效了。這樣寫編譯失敗。因?yàn)?typename 直接修飾到了 foo(),而 foo() 是函數(shù)調(diào)用,顯然不是一個(gè)(位于Entity類內(nèi)的)類型名稱。
那么我們加上括號(hào),強(qiáng)行改變結(jié)合率:
(typename Entity::Entity::Entity)::foo(); // 行嗎?也不行!
也不行,因?yàn)?typename 不是一個(gè)操作符,沒有優(yōu)先級(jí)這一說(shuō)。 實(shí)際上,編譯器(g++/clang)看到 typename 前面有個(gè) 左括號(hào),就直接報(bào)錯(cuò)了。
顯然,我們按照所謂“A::A”的特例,來(lái)解釋 “Entity::Entity::Entity::foo()”的合法性,是走不通的。并且還不能怪C++標(biāo)準(zhǔn),只能怪我們自己,因?yàn)槿思覙?biāo)準(zhǔn)說(shuō)很清楚,是 A::A ,或都 A::A::A,而不我們要解釋的,其實(shí)是 A::A::F 。最后的符號(hào) 是F而不是A,不滿足特例。
Entity::Entity::Entity::Entity::Entity::Entity::foo() 竟然編譯成功?這一切的背后,原來(lái)既不是人性的扭曲,更不是道德的淪喪,而是我們眼花看錯(cuò)了,原來(lái) Entity::Entity::foo 并不符合 C::C 的特例,是我們自己想多而已。
真是豁然開朗??!原來(lái)這就是一個(gè)普普通通的 “Qualified name lookup”嘛!就是 C::F嘛!只不過(guò)是 C寫了好多幾次,變成: C::C::C::F 而已嘛!結(jié)合本例,把 C 用 Entity 代入,把 F 用Entity 的靜態(tài)成員調(diào)用 foo() 代入,得到:
Entity::Entity::Entity::foo();
按照 “Qualified name lookup” 規(guī)則,:: 左邊的 Entity 應(yīng)優(yōu)先按 類名查找,于是找到 class Entity,并且它里面還正好有 foo 成員,并且還正好是 一個(gè)靜態(tài)成員,可以直接通過(guò) 類名來(lái)調(diào)用,這就是: Entity::foo()。
切慢,前面還是有一大串 Entity::Entity:: 怎么解析或解釋?也好辦, 既然已經(jīng) 是 C::F 形式,而不是特例 “C::C”形式,于是有關(guān) Entity::Entity 是一個(gè)構(gòu)造函數(shù)名字的選項(xiàng),就已經(jīng)失效,此時(shí)統(tǒng)一走 “Qualified name lookup”, 再結(jié)合 “Injected-class-name” 規(guī)則, Entity::Entity 就是得到類名 “Entity”,于是再多層 “Entity::Entity::Entity::Entity”,兩兩結(jié)合后,最終得到仍然是 一個(gè)類名:“ Entity”。而 類名 + :: + 靜態(tài)成員函數(shù),比如: “Entity::foo()”,不就是一次再普通不過(guò)的靜態(tài)成員函數(shù)的調(diào)用嗎?
夜色已深,古老的C++部落再次恢復(fù)它的安寧。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-530875.html
各位不要打我,其實(shí)我還是講了很多C++方面的科學(xué)知識(shí)的。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-530875.html
到了這里,關(guān)于C++中,C::C::C::C::foo() 為什么編譯成功?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!