標籤:

繼承模板類為什麼可以用this訪問基類?

effective C++ 中43條中提到三種訪問模板基類的方法,另兩個還行,為什麼用this也可以?這不是派生類嗎,this也應該是派生類的對象指針呀。初學者不懂,忘大神指教:如下

template&
class A
{
public: void f()
{ cout &<&< "A::f()" &<&< endl; } } template&
class B:public A&
{
public: void f()
{
this-&>f();//加this-&>為什麼可以訪問基類,這特意加了一個同名的,也同樣訪問基類的
A&::f();//也可以這個我懂
}
}


關鍵點在於:不加 this,編譯器就會把 f 當作一個非成員函數,因此才報的錯。你沒看錯,是「非」成員函數。

這事得從模板的「二段式名字查找」(Two-Phase Name Lookup)說起。

根據 C++ 標準,對模板代碼中的名字的查找,分為兩個階段進行:

  1. 模板定義階段:剛被定義時,只有模板中獨立的名字(可以理解為和模板參數無關的名字)參加查找
  2. 模板實例化階段:實例化模板代碼時,非獨立的名字才參加查找。

有了這個背景知識就可以來分析這段代碼了:

template&
class A
{
public:
void f() { std::cout &<&< "A::f()" &<&< std::endl; } } template&
class B: public A&
{
public:
void g() {
f();
}
}

如果沒有用模板,事情會簡單很多。然而這裡的 B 本身是模板,需要進行二段式名字查找。

首先進入 B 的模板定義階段,此時 B 的基類 A& 依賴於模板參數 T,所以是一個「非獨立」的名字。所以在這個階段,對於 B 來說 A& 這個名字是不存在的,於是 A&::f() 也不存在。但此時這段代碼仍舊是合法的,因為此時編譯器可以認為 f 是一個非成員函數。

當稍晚些時候進入 B 的模板實例化階段時,編譯器已經堅持認為 f 是非成員函數,縱使此時已經可以查到 A&::f(),編譯器也不會去這麼做。

「查非成員函數為什麼要去基類裡面查呢?」於是就找不到了。

那我們回過頭來看 this-&>f():

  • 模板定義階段:儘管沒法查到 A&::f(),但明晃晃的 this-&> 告訴編譯器,f 是一個成員函數,不是在 B 類里,就是在 B 類的基類里,於是編譯器記住了
  • 模板實例化階段:此時編譯器查找的對象是一個「成員函數」,首先在 B 中查,沒有找到;然後在其基類里查,於是成功找到 A&::f(),功德圓滿。


第一贊已經講的很好了。二段式命名查找,就是這個概念。

lz你最開始提問的那段代碼應該是會導致遞歸調用棧溢出。

如果是第一贊那位的代碼(f改成g),就是如同他解釋的那樣。

另外,這個二段式查找的實現,也和不同編譯器版本的特性支持有關。似乎很老的編譯器如vc6.0之類就不支持。


2015-07-01 補充

這篇文章很好的講解了二段查找及其所解決的問題:Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookup。同時,文章和評論也討論了升級到支持二段查找的編譯器版本可能break現有代碼行為,有些會產生error(比較好解決),有些則沒有警告(難以發現),比如:

void foo( int a ) { /* Some definition */ }
// Now write a template that uses foo in a non-dependent way
// but passing in a char parameter.
void foo( char a ) { /* Some definition */ }
// Now use the template.

所以如果有模板代碼,必須檢查是否有unqualified but dependent names並加以更正。。。以保證代碼的跨編譯器一致性。

-------------------------------------------------------------------------------------------------------------------------------

任何一個抽象的模板類,在你用到它的時候(instantiate),他已經變成了一個具體類(模板參數已經被替換)。所以你的問題等價於:下面這個代碼為什麼成立?你體會一下:

class A
{
public:
void f() { cout &<&< "A::f()" &<&< endl; } } class B : public A { public: void g() { this-&>f(); }
}

@邱昊宇 我說我怎麼沒注意到過 Two-Phase Name Lookup,MSVC好像沒那麼嚴格。其他編譯器對下面代碼會報錯嗎?

template&
class A
{
public:
void f() { std::cout &<&< "A::f()" &<&< std::endl; } }; template&
class B : public A&
{
public:
B() { std::cout &<&< "Test() "; } void g() { --fxx(); } }; int main() { B& b;
//b.g(); // 用到這句編譯器才會給出error

return 0;
}


this-&>f()

這個訪問的是 B::f() ,不是 A&::f()

你自己編譯運行一下,明顯死循環,棧溢出。


按照題目中的現象:

template&
class A
{
public: void f()
{ cout &<&< "A::f()" &<&< endl; } } template&
class B:public A&
{
public: void f()
{
this-&>f();//加this-&>為什麼可以訪問基類,這特意加了一個同名的,也同樣訪問基類的
A&::f();//也可以這個我懂
}
}

這樣不管加不加this,都會出現堆棧溢出。 函數f中一直遞歸調用,沒有出口。

覺得LZ的原意應該是想問,

template &
class A
{
public:
void f() {printf("A");}
};

template &
class B : public A&
{
public:
void g()
{
f();
this-&>f(); //?
A&::f();
}
};

這個應該跟編譯器實現有關,至少VS2010 這樣編譯不會有問題。


應該是未定義行為吧。

第一階段猜測錯誤的話(指編譯器認為f()處於全局空間內),第二階段可以彌補:重新查找依賴名字即可。

PS:gcc編譯失敗,msvc編譯通過。


推薦閱讀:

c++模板類拷貝構造函數的問題,有點疑惑?
std::move(expr)和std::forward(expr)參數推導的疑問?
C++ delete[] 是如何知道數組大小的?
面向對象編程(oop)從誕生到現在理念上經歷了幾次怎樣大的變遷和轉化?
C++ 11為什麼引入nullptr?

TAG:C | 模板C |