2010-08-24 31 views
73

爲什麼友誼至少不能在C++中繼承?我明白傳遞性和反思性被禁止,原因很明顯(我只是說這只是爲了阻止簡單的常見問題引用答案),但是沿着virtual friend class Foo;的路線缺乏一些東西使我困惑。有人知道這個決定背後的歷史背景嗎?友誼真的只是一個有限的黑客,因爲它已經找到了一些晦澀的可敬的用途?爲什麼C++不允許繼承友誼?

編輯澄清:我說的是以下情形,其中A的兒童接觸到B或對B和它的孩子。我也能想象可選授權訪問友元函數覆蓋等

class A { 
    int x; 
    friend class B; 
}; 

class B { 
    // OK as per friend declaration above. 
    void foo(A& a, int n) { a.x = n; } 
}; 

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ }; 

接受的答案:Loki states,效果可以模擬在friended基類進行保護的代理功能,所以有更多或更少沒有嚴格的需要授予友誼的類或虛擬方法heirarchy。我不喜歡使用樣板代理(好友基地有效地成爲代理),但是我認爲這比在大多數情況下更可能被誤用的語言機制更可取。我認爲這可能是我購買和閱讀Stroupstrup的The Design and Evolution of C++,我已經看到足夠的人在這裏推薦,以獲得更好的洞察這些類型的問題的時間...

回答

69

因爲我可能會寫Foo及其朋友Bar(因此存在信任關係)。

但是,我相信編寫從Bar派生的類的人嗎?
不是。所以他們不應該繼承友誼。

對類的內部表示的任何更改都需要修改依賴於該表示的任何內容。因此,班級的所有成員以及班級的所有朋友都需要修改。

因此,如果的Foo內部表示被修改然後Bar也必須改變(因爲友誼緊密結合BarFoo)。如果繼承友誼,那麼從Bar派生的所有類別也將與Foo嚴格綁定,因此如果Foo的內部表示形式發生更改,則需要修改。但我不知道派生類型(我也不應該,他們甚至可能由不同的公司開發)。因此,我將無法更改Foo,因爲這樣做會在代碼庫中引入突變(因爲我無法修改從Bar派生的所有類)。

因此,如果友誼被遺傳了,你無意中引入了對修改類的能力的限制。這是不可取的,因爲你基本上渲染了公共API的概念。

注意:Bar的孩子可以通過使用Bar來訪問Foo,只是使Bar中的方法受到保護。然後,Bar的孩子可以通過調用其父類來訪問Foo

這是你想要的嗎?

class A 
{ 
    int x; 
    friend class B; 
}; 

class B 
{ 
    protected: 
     // Now children of B can access foo 
     void foo(A& a, int n) { a.x = n; } 
}; 

class D : public B 
{ 
    public: 
     foo(A& a, int n) 
     { 
      B::foo(a, n + 5); 
     } 
}; 
+1

Bingo。這是關於通過改變一個班級內部造成的傷害限制。 – 2010-08-25 02:12:31

+0

@Jeff:http://www.drdobbs.com/184402053 – 2010-08-25 03:05:26

0

猜測:如果一個類聲明某個其他類/函數作爲朋友,這是因爲第二個實體需要特權訪問第一個實體。在授予第二個實體有什麼用處,從第一個實體獲得任意數量的類的特權?

+2

如果A級通緝授予友誼B和它的後代,它可以避免更新其接口爲每子類增加或迫使B寫傳遞樣板,這是我認爲的第一位友誼的一半。 – Jeff 2010-08-24 23:05:29

+0

@傑夫:啊,那我誤解了你的意思。我認爲你的意思是'B'可以訪問從'A'繼承的所有類... – 2010-08-24 23:08:13

6

C++標準,第11.4節/ 8

友誼既不繼承也不傳遞的。

如果友誼會被繼承,那麼一個並不意味着成爲朋友的類將突然有權訪問您的類內部並且違反了封裝。

+1

說Q「朋友」A,而B是從A派生出來的。如果B繼承了A的友誼,那麼因爲B是A的一種類型,在技術上它是一個可以訪問Q的私人的A。所以這不會以任何實際的理由來回答這個問題。 – mdenton8 2015-06-25 20:02:46

0

派生類只能繼承基類的「成員」。朋友的聲明是而不是是一個友誼班的成員。

$ 11.4/1」 ......一個朋友的名字是 不能在類的範圍和 朋友不與成員 接入運營商(5.2.5)呼籲,除非它是 是另一班的成員。「

$ 11.4 - 「此外,由於友元類的基本條款 不是它 成員聲明的一部分,友元類的基本條款 不能訪問私有的 名稱和保護 成員友誼等級授予 。「

和進一步

$ 10.3/7-「[注:虛擬符 意味着會員資格,所以一個虛擬 函數不能是一個非成員(7.1.2) 功能也不能虛擬。函數 是一個靜態成員,因爲虛函數調用依賴於特定的 對象來確定要調用哪個函數 在一個類中聲明的虛函數可以聲明爲朋友 其他類。 ]」

因爲‘朋友’不是在首位的基類的一個成員,怎麼能由派生類繼承的?

+0

儘管通過類似成員的聲明給予友誼,但其實並不是真正的成員,其他類別的通知實質上可以忽略對「真實」成員的可見性分類。雖然您引用的規範部分解釋了語言如何運用關於這些細微差別和框架行爲的自洽術語,但事情本來可能會有所不同,並且不幸的是,上述任何內容都不會成爲理論基礎的核心。 – Jeff 2010-08-25 03:07:50

7

甲friended類可以通過訪問函數暴露其朋友,然後通過這些授予訪問權限。

class stingy { 
    int pennies; 
    friend class hot_girl; 
}; 

class hot_girl { 
public: 
    stingy *bf; 

    int &get_cash(stingy &x = *bf) { return x.pennies; } 
}; 

class moocher { 
public: // moocher can access stingy's pennies despite not being a friend 
    int &get_cash(hot_girl &x) { return x.get_cash(); } 
}; 

這允許更精細的控制比可選傳遞性。例如,get_cash可以是protected或可以執行的運行時間限制訪問的協議。

+4

我誤以爲陰莖便士... – Hector 2016-11-04 09:42:00

+0

@Hector:投票重構! ';)' – 2016-12-15 09:03:26

2

因爲這只是沒有必要的。

關鍵字friend的用法本身就是可疑的。就耦合而言,這是最糟糕的關係(繼承和組合之前的方式)。

對一個班級內部的任何改變都有可能影響這個班級的朋友......你真的想要一個未知數的朋友嗎?如果那些從他們那裏繼承的人也可能成爲朋友,那麼你甚至無法列出他們,而且你每次都有可能破壞你的客戶端代碼,當然這不是可取的。

我自由地承認,對於家庭作業/寵物項目而言,依賴往往是一個很遠的考慮因素。在小型項目上並不重要。但是,只要有幾個人在同一個項目上工作,這種情況就會增長到幾十條線路中,您需要限制更改的影響。

這帶來了非常簡單的規則:

更改類的內部應該隻影響該類本身

當然,你可能會影響它的朋友,但有2箱子這裏:

  • 朋友免費功能:可能更多的成員函數的反正(我想在這裏std::ostream& operator<<(...),這是不是一個成員純粹的語言規則事故
  • 朋友課?你不需要真正的課堂上的朋友課程。

我會建議使用簡單的方法:

class Example; 

class ExampleKey { friend class Example; ExampleKey(); }; 

class Restricted 
{ 
public: 
    void forExampleOnly(int,int,ExampleKey const&); 
}; 

這個簡單的Key模式允許你聲明的朋友(的方式),而實際上是給它訪問你的內部,從而孤立它從變化。此外,如果需要,它可以讓這位朋友把鑰匙借給受託人(如兒童)。

29

爲什麼友誼至少不能在C++中繼承?

我認爲你的第一個問題的答案是在這個問題上:「你父親的朋友可以接觸你的私人嗎?」

+16

公平地說,這個問題引起了你父親的不安。 。 。 – iheanyi 2015-08-07 16:14:03

+1

@iheanyi:這是關鍵。 \ o/ – wilx 2015-08-07 16:15:14

+1

這個答案是什麼意思?充其量只是一個可疑的,儘管可能輕鬆的評論 – DeveloperChris 2016-04-12 02:15:13

0

在一個類中的朋友函數將extern屬性分配給該函數。即,外部表示該功能已經在班級外的某個地方進行了聲明和定義。

因此它意味着朋友功能不是一個類的成員。所以繼承只允許你繼承一個類的屬性而不是外部的東西。而且,如果友元函數允許繼承,則繼承第三方類。

0

朋友是喜歡的風格界面容器 但對我來說繼承好,先說,C++缺乏繁殖的遺傳

class Thing; 

//an interface for Thing container's 
struct IThing { 
    friend Thing; 
    protected: 
     int IThing_getData() = 0; 
}; 

//container for thing's 
struct MyContainer : public IThing { 
    protected: //here is reserved access to Thing 
     int IThing_getData() override {...} 
}; 

struct Thing { 
    void setYourContainer(IThing* aContainerOfThings) { 
     //access to unique function in protected area 
     aContainerOfThings->IThing_getData(); //authorized access 
    } 
}; 

struct ChildThing : public Thing { 
    void doTest() { 
     //here the lack of granularity, you cannot access to the container. 
     //to use the container, you must implement all 
     //function in the Thing class 
     aContainerOfThings->IThing_getData(); //forbidden access 
    } 
}; 

對我來說,C++的問題是缺乏很好的粒度的 控制從任何地方訪問所有的任何東西:

朋友東西可以成爲朋友的事*授予事情

多,朋友[評爲區]事情的所有子訪問。*授予準確訪問權限 位於Container類中,通過該朋友的特定命名區域。

好吧,停止夢想。但現在,你知道朋友的一個有趣的用法。

在另一個命令中,你也可以發現有趣的知道所有的類都與自己友好。換句話說,一個類的實例可以調用另一個同名實例的所有
成員不受限制:

class Object { 
    private: 
     void test() {} 
    protected: 
     void callAnotherTest(Object* anotherObject) { 
      //private, but yes you can call test() from 
      //another object instance 
      anotherObject)->test(); 
     } 
};