C++允許「我們都是人,所以我可以把你私有的眼睛借來隨便玩,再還給你」,這難道是一種設計上的妥協?

P.S.:OO哲學在知乎好像是個不太受待見的詞,但這個問題讓我覺得很有興味,突然感覺這面向對象思想很集體主義

C++入門,最近突然意識到:同一個類不同對象可以互相訪問彼此的私有成員,於是有了如題的疑問。

我認為,這樣來講私有性對對象而言是無意義的,僅僅對類而言才有私有,訪問限制居然是面向類的?我了解到Java似乎也是這樣的,這樣是符合OO哲學的么?還是單純是一種設計上的妥協?

示例代碼:

#include&
using namespace std;

class MyClass
{
public:
MyClass(string);
string GetName(MyClass);
private:
string Name;
};

string MyClass::GetName(MyClass SomeOne) { return SomeOne.Name = "共產主義接班人"; }
MyClass::MyClass(string InputName) :Name(InputName) {}

int main()
{
MyClass a("團支書"), b("孫同學");
cout &<&< a.GetName(b).c_str() &<&< endl; cin.get(); return 0; }

等等·······我叫啥來著?


作為 class Person 的作者,你問自己一個問題:

我如何讓別人的 class 不能訪問 Person 的某個成員?

答案是「用 private」。

然後你又問自己一個問題:

我如何讓一個 Person 的 object 不能用另一個 Person object 的某個成員?

答案是「我 tm 都是作者了!」「Don"t hold it that way.」—— Steve Jobs

你還是有疑問:我的 class Person 要是特別大特別複雜,寫了後面忘了前面怎麼辦?

對於寫了後二十行忘了前二十行還必須一個函數寫二百行的你說我一個設計語言的我怎麼辦?


private本來就是個騙局,java里private甚至final都能通過反射改掉。

再說了private本來就是阻止外界訪問,內部做什麼就不好管了。

真要說的話getname要加const。

而getname作為非靜態方法,不應該加入對實例的引用這麼一個參數。

哪怕作為一個靜態方法,傳入的引用也要加const,畢竟這個方法意義上就不該改變內容。

========================突然想到一點========================

如果要追求oo,對象和對象之間交互要通過調用方法,只有修改自己的內容才直接修改屬性。而你傳進去的雖然是同個類,但已經是另一個對象了。

所以歸根究底,還是這代碼本身違背了那些oo的準則吧?

===========================================================

c++不會阻止你故意犯錯,他認為你寫的每一行代碼都是有自己意義的,最多就是編譯器對你if(a=b)這樣的潛在錯誤的代碼報個warning罷了。

你說的情況其實也不是沒有道理,比如團支書擁有極大的權利,把每個和他接觸的人改xi名nao成共產主義接班人呢?

就算name只能通過一個方法去改變,也沒法驗證調用方有沒有這個權利。這種事情存在於開發者腦中,隱藏於用戶操作之下就夠了。

順便吐槽一下,java里放了一堆private,又加上一堆默認的getter和setter的行為,簡直是為了oo而oo。


別的 OO 語言(Java/C#)也可以,所以這怪不到 C++ 頭上。另外,我反對用 C++ 友元來解釋,因為 Java/C# 沒有友元,但同樣能訪問同類型的其他對象的 private 成員。

// Java
public class Person {
private long age = 0;

public void plus1s(Person elder) {
elder.age += 1;
this.age -= 1;
}
}


這點我也注意過,(在類里操作同類型的this和other,沒區別,問題不在此),我猜測你主要疑惑:在成員函數里讀寫成員變數不太安全和定位BUG。

那麼有下面幾種辦法(目標都是減小直接讀寫成員變數的範圍):

1:對複雜的set單獨寫一個private函數

2:把牽扯比較大的成員變數單獨放到一個內部類里(提供get set )

3:把你這種類似 string GetName(MyClass); 的寫成 static非成員函數。

這只是一個小方面,關於OO,你應該更關注多態與復用。推薦一篇好文章:

《再談面向對象的三大特性》


封裝的主要目的是為了你把你的類提供給另一個開發者的時候,他不用關心你的私有實現,看你的介面就行了,省他的事(少看代碼)也省你的事(方便重構)。如果他用反射等機制破開這層限制那是他越界,就是他自己的鍋,你重構把他鍋搞炸了也不是你的錯。

同一個類的不同實例之間一般不存在這個問題,一般會出現一個類Foo的實例foo1是你開發的,foo2是張三開發的這種情形么?並不會。

於是不需要封裝。

OO的設計目的是解決軟體工程上的問題,不是貼近真實地模擬現實世界。


這可能是為了處理拷貝構造函數,和operator=(const ..)帶來的一系列問題,這很關鍵。

默認的拷貝構造函數和operator=(const ..),會調用每個成員的拷貝函數。如果同類的私有成員無法訪問,那如何對其進行默認拷貝。否則只有那些全部是public的可以生成默認拷貝和賦值,或者用戶來手工寫這些函數,並且必須要求private的暴露個getter出來,這太扯淡了。所以乾脆設為可以訪問的算了。

你試著用其他語言寫個拷貝函數之類的看看,你就知道這個特性其實非常好。


「哲學意義去說是不是設計上的妥協」,感覺這是個挺有趣的問題,以前從來沒用類似的角度思考過。我沒法去上升到哲學和設計這兩塊,但從對語言范型理解和自己的微薄體會說說。

我的理解:面向對象本身是一種編程泛型/編程方法,教科書上的三大特性:封裝性,繼承性,多態性。但主要目的還是能讓程序更易復用,易維護,容易抽象。

而可訪問性即所謂private protect,最早期的一些面向對象語言並不具備,常用的python中也是通過編碼命名規範去隱式體現可訪問性。引入可訪問性可能是想讓模塊更內聚,避免耦合,提升抽象性等等。

單回答這個問題,面向對象哲學中,(從封裝性出發)一定是不希望出現「人非常私有的眼睛被其他人借用的情況」,如果你的設計初衷是希望其他模塊只依賴你的可見部分。但如果語言層面這麼限制死了,也許就失去了靈活性,失去了易用性。比如你發布了一個human.dll,我就想用一個god.dll來應用你的dll的結構,hack一下搞出來個變種人,這時候可能可見性可能又有了更多的等級。

你的這個例子中,cpp中通過引用/操作內存方式可以存取私有結構,是cpp靈活和面向底層的體現,如果自己的代碼盡量避免這麼繞的使用吧,但如果面對別人寫的這樣的代碼,你沒法改,卻又想要擴展和復用它,那麼你可能就需要類似的hack。

java種實現類似的hack,最常用的就是反射,目的仍然是讓程序實現更靈活。

完全循編程範式實現的語言,估計會教條和不切實際,不靈活不通用,沒法玩一些hack啊黑科技,肯定不太能討好程序員。

你看看人家javascript,三種範式都玩的轉,討喜的緊,到處都在用:)


不,真正的意思是這樣的,我是上帝,你們兩個都是人,所以我可以把你們……


其實一個類是否可以去訪問另一個類的私有成員

只能通過友元關係來確定

所以問題就變成一個類型是否是該類型自身的友元呢?

答案是肯定的

所有類都是該類自身的友元

因為所有非靜態成員函數實際上等價於一個隱藏了this指針參數的靜態成員函數

所以非靜態成員函數和靜態成員函數沒有本質區別

既然一個非靜態成員函數可以通過this指針參數去訪問它自身的私有成員

唯一的解釋就是該類和該類自身是友元關係

你說到為什麼友元關係只能作用於類 而不能作用於類的實例

訪問修飾符和友元都只能用來確定編譯期可見性關係

很顯然編譯期是無法確定實例間的可見性關係的(因為實例還不存在)

想實現這種需求只能在運行期通過自己實現代碼來確定各個實例之間的可見性

在運行期拋出異常來實現


既無可能,也無必要

——印象中來自Thinking in c++


你搞錯了,面向對象是對象類型間的隔離,而不是實例之間的隔離。

否則就是面向實例的語言。


OO的許可權封裝是針對類的,而非對象,這個應該是大多數(就我知道的語言是所有)的實現

如果針對對象,那類裡面的static方法就沒啥意義了,因為跟外面的extern function沒區別了就;而反過來,由於每個類的方法的代碼編譯後只有一份,那就意味著編譯器在看到某個方法,比如f中訪問參數a的屬性時,由於不清楚a和this是不是一個對象,就沒法判定是否允許訪問了

實際上就算在不支持傳統OO這種形式的語言中,許可權這東西也是針對代碼的,而非運行時生成的對象,比如golang的許可權是針對package的等

動態類型語言由於編譯器不知道一個變數的具體類型,所以私有屬性就很難做,可以看看python的實現,是變相地改名來做,在私有屬性名字前面加類名來識別,同一個類的不同對象的私有屬性名字相同,即可隨便訪問了,但實際上類名相同的不同類也是friend


private 只是不同類之間的一道牆,同一個類內一切成員皆 public。


各種從OOP角度給這個問題強行洗地的回答是沒有希望的。訪問別的對象的實現細節對於OOP來說是根本性的錯誤。

問題其實在於C++用同一套語法支持OOP和ADT(抽象數據類型)。後者比如std::complex,如果用OOP風格實現(比如不允許直接訪問其它對象的private)而不損失性能,需要在發明C++時還不普及的強力優化。

C++大半的複雜性都可以歸結到這個(Stroustrup引以為豪的)multi-paradigm上。


OO是用來做工程的,不是用來研究哲學的,能不能不要把你的日常經驗帶進來……訪問許可權從一開始就是針對類型的,對於不同的代碼來說類成員的可見級別不同,這樣對於外部代碼來說是一個包含了所有public成員的類型,對於繼承類來說是public+protected,對於內部代碼來說是public+protected+private,這個差別取決於代碼對於這個類型的了解程度。

對C++來說類的成員方法是類的一部分,自然是所有實例共享的,它只是剛好有個參數是this而已,既然所有實例共享,那自然應當能訪問所有實例。私有針對實例有個毛線用哦,你的實例那麼厲害,怎麼不讓它上天……


假設有兩個人,其中一個A把自己或者克隆(作為參數)送給另一個人B,那還不是為所欲為?

person類是一個造人機(。。。),造出來的人有怎樣的功能由作者(上帝)定義,作者想讓他們具有特異功能都行,畢竟造人機是作者造的……


我想題主沒有理解封裝的一個功能是為了更好的控制。比方說你直接對變數賦值和使用一個Set方法賦值的區別是你可以修改那個Set方法控制賦值的過程,這相當於你把可能出現的大量的賦值行為通過Set給歸於這個入口,那麼你只需要處理這個入口就行了(比方說你可以區別輸入正負值的情況)。

PS:我是很反感沒經過考慮就教條式地將一個變數給聲明私有並加上Get,Set。現在重構的工具這麼方便,還不如在真的想對變數做約束的時候再添加Set方法。


這例子舉得,看一遍笑一遍

以前還真沒注意過這個問題。就我所知,類中的這種私有限制,都不是什麼實際內核或者內存限制,不像你程序不能訪問別的程序的虛擬內存一樣,而是編譯器上的限制,所以總有一種方式繞過。Python甚至都不用什麼麻煩的操作,可以直接訪問所謂的__私有成員。Java好像也可以。因此C++,可能稍微麻煩點,也可以在完全符合標準的情況下繞過限制。你舉的例子就是一種符合的標準的繞過。除此之外,指針強轉,甚至#define private public都可以。

我個人感覺確實是一種設計妥協,因為從理論上來講,我的private就是我的private,除了我誰都不能動,但沒有辦法真正限制讓你不動。啥時候有個編程語言能夠給類的private單獨劃分一個內存空間,做到除了自己真的別人都不能訪問,就好了。

其實我一直想問,如何建立讓子類不繼承還能讓外部訪問到的屬性。


對於一個C++的類,訪問它「人」可以分為兩種:類的設計者,類的用戶。private是為了限制類的用戶而生的。

類的設計者與類的用戶目的是不一樣的,類的設計者有義務通過private來保證類的完整性。舉個栗子如下~

我是類的設計者,我要製作一個鏈表類,類中有兩個private成員:指針p指向首個結點,n記錄鏈表結點的個數。同時我還設計了許多public的介面函數,當然函數的定義也是我來寫。類的用戶不能直接訪問p與n,但是可以通過介面函數來使用鏈表。我的責任是,時刻保持p指向的鏈表結點數與n相等。private的作用正是來幫助我這個「類的設計者」:由於n與p只有我能訪問,只要我介面設置合理,無論用戶怎麼操作,我都不用擔心n與p之間產生任何矛盾,比如p指向nullptr而n還不等於0。所以說,private的作用是防止類的完整性被「類的用戶」破壞。

所以,這個問題就解決了~

(再來看題主的例子,無論怎麼通過reference來訪問私有成員,都是在類成員函數的定義中訪問的(其實也就是「類的設計者」訪問的)。private本來就沒有限制「類的設計者」的用途的,就像下面的代碼同樣也是合法的。)

class Test
{
public:
Test(int data=0):_data(data){}
int fu()
{
Test atest;
return atest._data;
}
private:
int _data;
};
void main()
{
Test a(2);
cout&<&


我認為這個標題體現了面向對象容易陷入的一個誤區——拿現實事物來比喻OOP中的對象。表面上看是容易理解,然而真實世界的物理規則很多實際上並不適合於軟體建模,所以這種比喻其實往往是有缺陷的。

就這個問題而言,語言規則的設置是否合理,本質上講是應該結合具體的使用場景來看的。題主說的拷貝構造函數我覺得就是一個好例子,因為要從另一個對象複製完整的對象屬性,有時候確實需要訪問對方沒有公開出來的成員,所以允許訪問對方的私有成員在這種場合下是合理的。像上面有人說到的,private的意圖是防止類的用戶無意破壞了不屬於他的對象,而不是用來限制類的設計者的,類的設計者完全可以允許自己設計的類之間互相訪問,並不違反OO的原則。


推薦閱讀:

visual studio和gdb的調試機制到底是怎麼樣的?
C++不用工具,如何檢測內存泄漏?
生物信息學需要掌握C++嗎?
如何實現一個C++反射庫?
如何勸說上級更新 GCC 和 VS 的版本,並把項目遷移至 C++11?

TAG:面向對象編程 | C | 集體主義 |