標籤:

C++派生類的成員或友員只能通過派生類對象來訪問基類的受保護成員?

這是 C++ Primer 第 5 版中的,第一條說的我理解,但是感覺第二條和第一條自相矛盾啊,派生類的成員或友員不是直接可以訪問基類的受保護成員嘛?怎麼又說只能通過派生類對象來訪問呢?


並不矛盾,第一條是:

老爸拿出他的卡,對兒子說:「我的卡就是你的卡,隨便拿去刷。」

第二條是:

兒子有個朋友,最近手頭緊。雖然是借同一張卡,但是問誰要是個技術活。

---

朋友:大哥,借我點錢花花?

兒子:拿去,這是我爹的卡,他的就是我的,你放心隨便刷。

---

朋友:叔叔,借我點錢花花?

老爸:你誰啊?誒誒!你 TM 伸手拿我的卡是幾個意思?

雖然作者事情說得沒錯,但我更傾向於認為這是 friend 的重要性質,而不是 protected 的。


意思就是說,當你有下面這兩個類的時候

class Fuck
{
protected:
int bitch;
};

class Shit : public Fuck
{
friend class Screw;
};

於是在Screw裡面可以看到Shit::bitch,不能看到Fuck::bitch。儘管看起來他們是同一個東西,但其實是不一樣的。Shit的成員就是Shit的成員,Fuck的成員就是Fuck的成員,Shit的成員並不包含Fuck的成員。反過來說,如果friend class寫在了Fuck裡面,你也是看不見Shit::bitch的。


這兩句話看起來是很矛盾, 主要還是因為作者試圖總結一套規律, 但是在我看來這套規律的作用範圍非常有限, 以至於很容易讓讀者狹隘的理解protected和friend的特性, 特別是作者把protected和friend揉在一起說, 第一次看簡直莫名其妙. 倒也不是翻譯的問題, 原文就是這麼寫的.

這裡講一下我自己的理解. 先看下面的代碼:

class FriendOfFather;

class Father {
protected:
int proint;

friend class FriendOfFather;
};

向前聲明了FriendOfFather, 因為它要作為Father的友元類. Father有一個protected成員proint.

接下來看Father的子類Son的代碼:

class Son : public Father {
public:
void testSon(Son s) {
s.proint = 1; // (1) pass compile
}
};

(1)能通過編譯.

這個很簡單, Son這個類繼承了Father的protected成員proint, protected成員對派生類可見, 所以可以直接訪問proint, testSon的參數是Son的引用s, s引用的就是testSon這個成員方法所在類本身的對象.

再看Son的子類Grandson的代碼:

class Grandson : public Son {
public:
void testGrandson(Son s) {
s.proint = 2; // (2) compile error
}
};

(2)不能通過編譯

再次重複, (1)能編譯, 是因為(1)中s引用的對象就是這個類自身的對象, 類內部可以隨意訪問類自身的成員.

儘管Grandson繼承自Son, Grandson可以向上轉型成為Son, 但是他倆根本就是兩個類, Grandson雖然繼承了Son, 但是這與他倆是兩種類型並不矛盾. 也就是說testGrandson中s引用的類Son和Grandson不是同一個類, 這是(2)與(1)不同的地方. 一個類的protected成員對外部是不可見的, 所以Grandson的成員方法自然不能直接訪問另一個類Son的對象s的成員proint. 儘管不能訪問s的成員proint, 但是Grandson是可以訪問this-&>proint, 這是通過繼承得到的成員.

最後看友元類FriendOfFather的代碼:

class FriendOfFather {
public:
void testFriend(Son s) {
s.proint = 3; // (3) pass compile
}
};

通過上面的描述, 可以知道(3)和(2)的情形其實是類似的, 在某個類的成員函數中直接訪問另一個類對象的成員, 不同的地方在於FriendOfFather是Father的友元, s繼承自Father, Son可以向上轉型成為Father, Father允許FriendOfFather直接訪問它的成員, 因此FriendOfFather中可以通過Son的引用直接訪問它繼承的Father成員, 但是由於friend是不會繼承的, 所以FriendOfFather也就只能訪問Son對象中繼承的Father成員, 而不能訪問Son自己定義的非public成員.

需要注意的是不要把Grandson, Son, Father混為一談, 他們的繼承關係允許了子類向上轉型, 但這三個類是不同的三個類, 即便Grandson能轉型成Son, 也不能直接在Son中訪問另一個Grandson對象的非public成員, 更別提在Grandson中訪問另一個Son對象的非public成員.


不得不反對 @vczh 的這個回答,你說的只是friend的性質,根本不是題主所說的protected的性質!!

題主的正確回答我來給:請看一下代碼片段

class Base {
Base() {
a = 10;
}
protected:
int a;
};

class Derived : Base {
Base baseObject;

void foo() {
a = 11; // target object is self (kind of Derived)
baseObject.a = 12; // target object is kind of Base
}
};

注意在Derived 的 foo() 的方法里,第一句是可以編譯通過的,第二句不行,會報錯

這正是因為,第一句

a = 11

操作的對象是Derived類型,因此他可以看到從Base繼承下來的protected a成員

相對,第二句

baseObject.a = 12

是不行的,因為他的操作對象明確地是一個與自身self無關的另一個Base類型對象,a成員是不可見的。

----

至於 @vczh 的回答,只是說明friend 聲明與繼承毫無關係

具體而言:

1. 基類聲明的友元,並不會自動成為派生類的友元

2. 派生聲明的友元,不會自動成為基類的友元


和題主一樣,看到這一開始也很疑惑,這句話作者也沒有說錯。派生類從基類繼承了public,protected和private成員,其中繼承而來的protected成員,派生類可以直接訪問(注意這裡說的訪問都是訪問自己類的,public不受限制),而基類的protected成員派生類無法訪問。也有點繞暈了。主要分清兩個概念:繼承而來的成員,還有基類的成員。


推薦閱讀:

變數名儲存在那裡,變數的儲存機制是什麼?
編程中你們都習慣怎麼使用大括弧?
C++中char是如何在地址中存儲的?
C++中為什麼說在構造器或析構器中使用異常處理會可能產生嚴重的問題?
如何理解 struct 的內存對齊?

TAG:C | CPrimer |