[英]Access of member functions in C++ inheritance
我對以下關於繼承的小程序感到困惑:
#include<iostream>
using namespace std;
struct B {
virtual int f() { return 1; }
}; // f is public in B
class D : public B {
int f() { return 2; }
}; // f is private in D
int main()
{
D d;
B& b = d;
cout<<b.f()<<endl; // OK: B::f() is public, D::f() is invoked even though it's private
cout<<d.f()<<endl; // error: D::f() is private
}
D::f()
是私有的, D
是公共繼承自B
,所以公共函數f
在B
D
也是公共的(我知道沒有繼承,默認情況下成員訪問是私有的) f
是B
的虛函數,所以如果我們調用bf()
,我們實際調用D::f()
,但正如圖中所提到的,為什么D::f()
能夠被調用,即使它是私有的? 任何人都可以詳細解釋簡單的繼承問題嗎?
這必須做到這一點,虛擬調度是一個運行時概念。 B
類並不關心哪個類擴展它,並且它不關心它是私有的還是公共的,因為它無法知道。
我無法弄清楚為什么D :: f()是私有的,D是公共繼承自B,所以B中的公共函數f在D中也是公共的(我知道沒有繼承,默認情況下成員訪問是私有的)
D::f()
是私有的,因為你把它變成了私有的。 此規則不受繼承或虛擬分派的影響。
f是B中的虛函數,所以如果我們調用bf(),我們實際調用D :: f(),但正如圖中所提到的,為什么D :: f()能夠被調用,即使它是私有的?
因為實際上,在調用bf()
,編譯器不知道實際調用哪個函數。 它將簡單地調用函數f()
,並且由於B::f
是虛擬的,因此將在運行時選擇被調用的函數。 運行時程序沒有關於哪個功能是私有還是受保護的信息。 它只知道功能。
如果在運行時選擇了該函數,則編譯器無法在編譯時知道將調用哪個函數,並且無法知道訪問說明符。 實際上,編譯器甚至不會嘗試檢查被調用的函數是否是私有的。 訪問說明符可能在編譯器還沒有看到的某些代碼中。
正如您所經歷的那樣,您無法直接調用D::f
。 這正是私人所做的:禁止直接訪問會員。 但是,您可以通過指針或引用間接訪問它。 您使用的虛擬調度將在內部執行。
訪問說明符僅適用於函數名稱 ,它們對通過其他方式調用函數的方式或時間沒有一些限制。 如果通過除名稱之外的某些方式(例如,函數指針)使私有函數可用,則可以在類外部調用私函數。
對於使用class
關鍵字聲明的class
,默認的訪問說明符是private
。 您的代碼與以下內容相同:
// ...
class D: public B
{
private:
int f() { return 2; }
};
如你所見, f
在D
是私有的。 對於具有相同名稱的B
的任何函數,訪問說明符是什么沒有區別。 請記住, B::f()
和D::f()
是兩個不同的函數。
virtual
關鍵字的作用是如果在引用D
對象的B
引用上調用沒有范圍限定符的f()
,那么即使它解析為B::f()
,實際上D::f()
也是相反調用。
這個過程仍然使用B::f()
的訪問說明符:在編譯時檢查訪問; 但是關於調用哪個函數可能是運行時間問題。
C ++標准有一個確切的例子:
11.5訪問虛函數[class.access.virt]
1虛函數的訪問規則(第11條)由其聲明確定,不受稍后覆蓋它的函數規則的影響。 [ 例如:
class B { public: virtual int f(); }; class D : public B { private: int f(); }; void f() { D d; B* pb = &d; D* pd = &d; pb->f(); // OK: B::f() is public, // D::f() is invoked pd->f(); // error: D::f() is private }
- 結束例子 ]
無法解釋清楚。
給出的答案說明了正在做什么,但為什么你會想要這樣做,基類調用private
虛函數?
好吧,有一種稱為模板方法模式的設計模式,它使用這種技術,它具有一個在派生類中調用私有虛函數的基類。
struct B
{
virtual ~B() {};
int do_some_algorithm()
{
do_step_1();
do_step_2();
do_step_3();
}
private:
virtual void do_step_1() {}
virtual void do_step_2() {}
virtual void do_step_3() {}
};
class D : public B
{
void do_step_1()
{
// custom implementation
}
void do_step_2()
{
// custom implementation
}
void do_step_3()
{
// custom implementation
}
};
int main()
{
D dInstance;
B * pB = &dInstance;
pB->do_some_algorithm();
}
這允許我們不將D
類的自定義步驟暴露給public
接口,但同時允許B
使用public
函數調用這些函數。
這實際上與虛擬調度關系較少,而與訪問說明符的含義有關。
功能本身不是private
; 它的名字是。
因此,函數不能在類的范圍之外命名,例如從main
。 但是,您仍然可以通過public
名稱(即被覆蓋的基本虛函數)或盡可能使用private
限定符(例如該類的成員函數)訪問函數名稱的作用域來執行此操作。
這就是它的工作原理。
為什么D :: f()能夠被調用,即使它是私有的?
要了解虛函數機制,最好知道它是如何實現的。 運行時的函數實際上只不過是函數體的可執行代碼所在的內存中的地址。 要調用該函數,我們需要知道它的地址(指針)。 內存中具有虛函數表示的C ++對象包含所謂的vtable - 一個指向虛函數的指針數組。
關鍵點是在派生類中,vtable重復(並可能擴展)基類的vtable,但是如果虛函數被覆蓋,則其指針將被替換為派生對象的vtable。
當通過基類指針完成虛函數調用時,虛函數的地址計算為vtable數組中的偏移量。 沒有進行其他檢查,只需要執行功能地址。 如果它是基類對象,它將是基類函數的地址。 如果它是派生類對象,它將是派生類函數的地址,無論它是否被聲明為私有都無關緊要。
這是怎么回事。
struct
成員默認為public,而class
成員默認為private。 所以B中的f()
是公共的,當它被導出到D時,因為你沒有明確地聲明它是公共的,所以根據推導規則,它變成了私有的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.