2016-09-17 65 views
15

在下面的例子中,我試圖通過使私人類Elayer隱藏從最後一個子類Designerusing Employee::showEveryDept -using聲明不能正常工作

#include <iostream> 

class Employee { 
private: 
    char name[5] = "abcd"; 
    void allDept() { std::cout << "Woo"; } 

public: 
    void tellName() { std::cout << name << "\n"; } 
    virtual void showEveryDept() 
    { 
     std::cout << "Employee can see every dept\n"; 
     allDept(); 
    } 
}; 

class ELayer : public Employee { 
private: 
    using Employee::showEveryDept; 

protected: 
    ELayer() {} 

public: 
    using Employee::tellName; 
}; 

class Designer : public ELayer { 
private: 
    char color = 'r'; 

public: 
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; } 
}; 

int main() 
{ 
    Employee* E = new Designer; 
    E->showEveryDept(); // should not work 

    Designer* D = dynamic_cast<Designer*>(E); 
    D->showOwnDept(); 
} 

但它仍然編譯和輸出 -

Employee can see every dept 
Woo 
Designer can see own dept 

但我已經明確地使它私有,請參閱 - private: using Employee::showEveryDept;

我在這做錯了什麼?

+1

@πάνταῥεῖ請告訴一個好的基於Linux的編譯器,它使用gui。我目前不太習慣直接使用gdb,也不太習慣購買windows。 –

+0

@hg_git如果你問我一個IDE的建議,我會建議Eclipse CDT。 –

+0

@hg_git然後是純命令行gdb。我對Eclipse很滿意,即使在我的跛腳筆記本電腦上也是如此。 –

回答

2

名稱具有以下性質:

  • 名稱 - 不合格標識符。
  • 聲明區 - 哪個類的名字被宣佈
  • 訪問 - 該區域中的名稱權。

這適用於名稱本身 - 不適用於名稱引用的任何變量或函數。可以使用相同的名稱命名相同的函數或變量,但使用不同的聲明區域。

當一個類被繼承時,派生類的聲明區域包括來自基類的所有名字;但訪問權限可以根據繼承類型進行更改:雖然只能聲明成員爲publicprotectedprivate,但在繼承之後,最終可能會有的成員無法訪問

這裏是你的代碼名稱和地區的訪問性的表格:

Name accessibility

注意如何tellName在所有三類公共,儘管它不是在Designer重新聲明。因此,ELayerusing Employee::tellName;是多餘的,因爲tellName無論如何將會是publicELayer

ELayer的效果的using Employee::showEveryDept;showEveryDept的內ELayer訪問private


名稱查找是解決該名稱區組合是由一個名稱的呼叫發現的過程。這個調用的上下文包括:

  • 調用位置,即範圍在名字被使用
  • 任何明確列出的範圍中的呼叫(例如Foo::name
  • 表達表示對象其成員正在訪問(例如,(*E)

訪問控制還考慮到:

  • 調用上下文並且其中名字被發現聲明性區域之間的關係。

例如,尋找在ELayer上下文高達showEveryDept會發現組合ELayer::showEveryDept訪問private

但在Employee的背景下查找相同的名稱將找到Employee::showEveryDept的組合。

無論這兩個組合是否引用相同的函數,此行爲都是相同的。

沒有再現調用的上下文如何轉化爲聲明的區域進行搜索,其規則的完整列表,使用:

`E->showEveryDept` 

查找名稱在靜態類型的*E的區域,這是Employee。它不使用動態類型,因爲名稱查找在編譯時解析。沒有運行時訪問錯誤 - 訪問是編譯時屬性。

訪問檢查的最後一步是比較publicEmployee與呼叫站點,即main()。規則是public授予所有呼叫站點的訪問權限,所以訪問檢查通過。


虛擬 -ness不依賴於名稱的屬性,也不在該名正在查找的範圍。與訪問不同,虛擬是函數的一個屬性,而不是任何名稱 - 區域組合。

虛擬調度處於活動狀態時,調用函數會將調用重定向到該函數的最終覆蓋。

從函數實現的角度考慮這一點非常重要,而不是函數的名稱。虛擬調度和訪問控制是兩個完全獨立的操作。僅當一個虛擬功能由不合格-ID,這意味着通過對前命名功能,而不Bla::稱爲

虛擬調度是活動的。

因此,在您的代碼中,E->showEveryDept確實會激活虛擬分派。訪問檢查如上所述通過,然後虛擬分派調用最終的覆蓋,這恰好是本示例中Employee中定義的主體。

在您的實際示例中,virtual由於未覆蓋該函數而沒有實際意義。但即使您在ELayer(而不是using聲明)中作爲私用函數覆蓋showEveryDept,它仍會調用該函數體。

+0

你必須是一個專業的老師,確切地知道我錯過了解這個問題:D –

+0

@hg_git嘿,謝謝你的獎勵......我不知道你是什麼失蹤,但我試圖擊中所有的基地 –

5

聲明

E->showEveryDept(); 

在編譯時已知的*E類型訪問showEveryDept。這是Employee,這個成員是可訪問的。

+1

你會介意糾正我嗎? 'Employee * E = new Designer;'定義了一個'Employee'類型的指針,但指向'Designer'類型的對象,對吧?所以'E> showEveryDept()'實際上是在'Designer'對象上調用'showEveryDept()'方法,或者不是?因爲那麼它很好隱藏! –

+0

是的,虛擬呼叫解析爲最派生的類。但是規則就像它們一樣,因爲在一般情況下,即使在全局分析的情況下,大多數派生類也不能在編譯時知道。例如。它可以取決於用戶輸入。 –

+3

不知道你是否認爲'私人'虛擬成員函數不能虛擬調用。他們能。許多人建議將虛擬成員函數設爲'private',但由於某些原因不明的原因(我不記得與Marshall討論過這個問題),這不是舊FAQ的建議,因此可能也不是ISO CPP FAQ。 –

4

在你main()功能,從而明確瞭解​​你的類型,並這樣稱呼它 -

int main() 
{ 
    Employee* E = new Designer; 
    E->showEveryDept(); // will work 

    Designer* D = dynamic_cast<Designer*>(E); 
    D->showOwnDept(); 
    D->showEveryDept(); // <-- Not legal now 
} 

這會產生錯誤 -

prog.cc: In function 'int main()': 
prog.cc:28:22: error: 'virtual void Employee::showEveryDept()' is inaccessible within this context 
    D->showEveryDept(); 
        ^
prog.cc:8:26: note: declared here 
      virtual void showEveryDept(){std::cout<< "Employee can see every dept\n"; 
13

您正在考慮它的錯誤的方法。

C++的概念是Name Lookup,它是一個很好的構思,並不經常出現在我們的腦海中,但是這種情況發生在使用name的任何地方。通過這樣做:

int main() 
{ 
    Employee* E = new Designer; 
    E->showEveryDept(); // should not work 

    Designer* D = dynamic_cast<Designer*>(E); 
    D->showOwnDept(); 
} 

線,E->showEveryDept()確實不合格的名稱查找對於屬於類的E的部件(在這種情況下成員函數)。由於它是accessible name,該程序是合法的。


我們,也知道DesignerELayer,其中showEveryDept()聲明private,你在這裏做得出:

class ELayer : public Employee { 
private: 
    using Employee::showEveryDept; 

protected: 
    ELayer() {} 

public: 
    using Employee::tellName; 
}; 

但你只是做了明確introduce名稱showEveryDept()Employee類成Elayer;引入到private訪問。也就是說,我們無法直接訪問與類成員/靜態函數外部的ELayer關聯的名稱(或稱爲該函數)。

ELayer* e = new Designer(); 
e->showEveryDept(); //Error, the name showEveryDept is private within ELayer 

然而,由於showEveryDept()具有基類的ElayerEmployerpublic訪問;它仍然可以使用qualified name lookup

ELayer* e = new Designer(); 
e->Employer::showEveryDept(); //Ok, qualified name lookup, showEveryDept is public 

Elayer這是在Designer訪問將其access specification來決定的名稱訪問。正如你所看到的,名字showEveryDept()是私人的,所以Designer甚至不能使用這樣的名字。

對於你目前的類層次結構和定義,這意味着,給定:

Employee* E = new Designer(); 
ELayer* L = new Designer(); 
Designer* D = new Designer(); 

- 對於unqualified lookup

  • 這工作

    E->showEveryDept();    // works! 
    
  • 這種失敗:

    L->showEveryDept();    // fails; its private in Elayer 
    
  • 這也將失敗:

- 對於qualified lookup,在這種情況下,請求從它的基類調用這樣的功能:

  • 這種失敗:

    D->Elayer::showEveryDept();  // fails! its private in Elayer 
    
  • 這工作:

    D->Employee::showEveryDept();  // works! its accessible in Employee 
    
  • 這也適用:

    L->Employee::showEveryDept();  // works! its accessible in Employee 
    

注意的是:引入任何成員函數的名稱,B::func(例如)從基類B,轉化爲派生類D,不覆蓋B::func,它只是使B::func在派生類的上下文中對重載解析可見。請參閱this questionthis的回答

+0

我的目標是讓它直接被'Employee'類對象調用,但如果被任何來自ELayer' –

+0

@hg_git,不,你不能這樣做...... – WhiZTiM

+0

這是一個非常好的答案,並且我接受其他人的唯一原因是因爲這對我更清楚。仍然你的回答確實幫了我很多:) –

0

我在做什麼錯在這裏?

你沒有做錯任何事。*

是預期的結果是錯誤的:

虛函數的訪問符檢查該類型員工,而不是設計師如你所料=>link

(*)除事實上,將訪問規則更改爲層次結構上的虛擬方法是我設想的糟糕設計=>check this。類成員的