簡體   English   中英

非朋友,非成員函數會增加封裝?

[英]Non-friend, non-member functions increase encapsulation?

非成員函數如何改善封裝的文章中,Scott Meyers提出無法阻止非成員函數“發生”。

語法問題

如果您像我與之討論過這個問題的許多人一樣,您可能會對我的建議的句法含義有所保留,即即使您購買了我的朋友,非朋友非成員函數也應優先於成員函數關於封裝的爭論。 例如,假設Wombat類支持進食和睡眠功能。 進一步假設進餐功能必須被實現為成員功能,但是睡眠功能可以被實現為成員或非朋友非成員功能。 如果您遵循我的建議,您將聲明如下內容:

 class Wombat { public: void eat(double tonsToEat); void sleep(double hoursToSnooze); }; w.eat(.564); w.sleep(2.57); 

啊,一切都統一! 但是這種統一是令人誤解的,因為世界上有比您的哲學所夢想的功能更多的功能。

坦率地說,非成員函數會發生。 讓我們繼續以袋熊為例。 假設您編寫了對這些獲取生物進行建模的軟件,並想象經常需要袋熊做的一件事情就是睡了半個小時。 顯然,您可以使用對w.sleep(.5)調用來w.sleep(.5)代碼,但這要輸入大量的.5,無論如何,如果魔術值發生了變化該怎么辦? 有很多方法可以解決此問題,但也許最簡單的方法是定義一個函數,該函數封裝了您要執行的操作的詳細信息。 假設您不是Wombat的作者,則該函數必須必須是non-member ,並且您必須這樣調用它:

 void nap(Wombat& w) { w.sleep(.5); } Wombat w; nap(w); 

到了那里,可怕的語法不一致。 當您想要喂食袋熊時,可以進行成員函數調用,但是當您希望它們午睡時,可以進行非成員調用。

如果您反映了一點並且對自己誠實,那么您將承認與所有使用的非平凡類都存在這種所謂的不一致之處,因為沒有一個類具有每個客戶端所需的所有功能。 每個客戶至少添加一些自己的便利功能,而這些功能始終是非成員的。 C ++程序員習慣了這一點,他們對此一無所知。 一些調用使用成員語法,而某些使用非成員語法。 人們只要查找適合他們要調用的函數的語法,然后就調用它們。 生活仍在繼續。 它在標准C ++庫的STL部分中尤其如此,其中一些算法是成員函數(例如,大小),一些算法是非成員函數(例如,唯一),而某些都是(例如,find)。 沒有人眨眼。 甚至沒有你

我真的不能把他的粗體/斜體字說的話全神貫注 為什么必須將其作為非成員實施? 為什么不只從Wombat類繼承您自己的MyWombat類,並使nap()函數成為MyWombat的成員呢?

我剛開始使用C ++,但這就是我可能會用Java做到的方式。 這不是使用C ++的方法嗎? 如果沒有,為什么呢?

從理論上講,您可以做到這一點,但您確實不想這么做。 讓我們考慮一下為什么您不想這樣做(目前,在原始上下文中-C ++ 98/03,而忽略了C ++ 11和更高版本中的新增功能)。

首先,這意味着基本上所有類都必須編寫為充當基類,但是對於某些類來說,這只是一個糟糕的想法,甚至可能與基本意圖背道而馳(例如,旨在實現的某些東西) Flyweight模式)。

其次,它將使大多數繼承毫無意義。 舉一個明顯的例子,C ++中的許多類都支持I / O。 就目前而言,慣用的方法是將operator<<operator>>重載為自由函數。 目前,iostream的目的是代表至少某種類似於文件的東西-我們可以在其中寫入數據,和/或我們可以從其中讀取數據的東西。 如果我們通過繼承支持I / O,那也意味着可以從任何模糊的文件中讀取/寫入的內容。

這根本沒有任何意義。 iostream至少代表某種類似於文件的東西,而不是您可能想要從文件讀取或寫入文件的所有對象。

更糟糕的是,這將使幾乎所有編譯器的類型檢查幾乎毫無意義。 僅舉例來說,將distance對象寫入person對象沒有任何意義-但是,如果它們都通過從iostream派生來支持I / O,則編譯器將無法從真正使感。

不幸的是,那只是冰山一角。 從基類繼承時,您將繼承該基類的限制 例如,如果您使用的基類不支持副本分配或副本構造,則派生類的對象也不會/也不能夠。

繼續前面的示例,這意味着如果要對對象執行I / O,則不能支持該類型對象的副本構造或副本分配。

反過來,這意味着支持I / O的對象將與支持放入集合中的對象脫節(即,集合需要 iostream禁止的功能)。

底線:我們幾乎立即以徹底難以處理的混亂結束,在此混亂中,我們的繼承都不再具有任何實際意義,並且編譯器的類型檢查將幾乎完全變得無用。

因為這樣您就在新類和原始Wombat之間建立了非常強烈的依賴關系。 繼承不一定是好的。 它是C ++中任何兩個實體之間第二強的關系。 只有friend聲明更強。

我認為當Meyers首次發表該文章時,我們大多數人都采取了雙重行動,但目前人們普遍認為它是真的。 在現代C的世界++你的第一反應應該是從類派生。 除非您要添加確實現有類專業化的新類, 否則派生是萬不得已的方法。

Java中的事項有所不同。 您在那里繼承。 您真的別無選擇。

正如傑里·科芬(Jerry Coffin)所描述的那樣,您的想法並不全面,但是對於不屬於層次結構的簡單類(例如此處的Wombat ,它是可行的。

但是,有一些危險需要提防:

  • 切片 -如果存在一個按值接受Wombat的函數,則您必須切斷myWombat的額外附件,並且它們不會重新增長。 在Java中通過引用傳遞所有對象的情況下不會發生這種情況。

  • 基類指針 -如果Wombat是非多態的(即沒有v-table),則意味着您不能輕松地在容器中混合WombatmyWombat 刪除指針不會正確刪除myWombat品種。 (但是,您可以使用shared_ptr來跟蹤自定義刪除器)。

  • 類型不匹配 :如果編寫任何接受myWombat函數,則無法使用Wombat調用它們。 另一方面,如果編寫函數以接受Wombat則不能使用myWombat的語法糖。 投射無法解決此問題; 您的代碼將無法與界面的其他部分正確交互。

避免所有這些危險的一種方法是使用包容而不是繼承myWombat將具有Wombat私有成員,並且為要公開的任何Wombat屬性編寫轉發功能。 myWombat類的設計和維護方面,這是更多工作。 但是它消除了任何人錯誤使用您的類的可能性,並且使您能夠解決諸如所包含的類不可復制之類的問題。


對於層次結構中的多態對象,盡管類型不匹配問題仍然存在,但您沒有切片和基類指針問題。 實際上,情況更糟。 假設層次結構為:

Animal <-- Marsupial <-- Wombat <-- NorthernHairyNosedWombat

您可以從Wombat派生myWombat 但是,這意味着NorthernHairyNosedWombatmyWombat的同胞,而它是Wombat的子代。

因此,無論如何, NorthernHairyNosedWombat都無法使用添加到myWombat中的任何漂亮的myWombat函數。


簡介:恕我直言,這些好處不值得它留下的爛攤子。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM