STRUCT vs. CLASS

關(guān)鍵字structC++繼承自C語言的一項遺產(chǎn)。作為更加貼切的詞匯,class 被引入C++,用來表現(xiàn)。這個決策造成的結(jié)果是:一種語言提供了兩個關(guān)鍵字來表示完全一致的概念。在什么情況下應(yīng)該使用誰,社區(qū)內(nèi)并無定論,甚至C++的發(fā)明者Bjarne Stroustrup也無法給出毫不含糊的建議。

一種流行的看法

如果只是名字的差別,那么毫無疑問,在任何時候都應(yīng)該使用class,畢竟它更直觀、準確。

很多C++開發(fā)者可能并不同意這一點。從他們的邏輯和經(jīng)驗出發(fā),struct 仍然只應(yīng)該用來定義那些只有數(shù)據(jù),沒有行為的“類”—— 它們事實上是C語言中的結(jié)構(gòu)體;而class則應(yīng)該用來定義真正的類——那些有行為的家伙。甚至有團隊規(guī)定: 一旦你使用struct來定義一個類型,則其就不應(yīng)該有任何行為;否則,就需要使用class

如果使用C++開發(fā)一套庫,卻要提供一套可供C語言調(diào)用的接口,這樣的規(guī)定是合理的。畢竟,C語言是不認識超出自己理解范圍的任何C++語法的。

如果不是基于此類目的,這種規(guī)定就是自找麻煩。一個類型是不是應(yīng)該有行為,是動態(tài)的。盡管最初你定義它的時候,它是以持有數(shù)據(jù)為主要目的。但你無法確保隨后你不會因為某種目的需要為其添加一個方法。

比如,最初的時候你定義了這樣一個數(shù)據(jù)結(jié)構(gòu),由于它是進程間通信的消息包,所以在你看來,它應(yīng)該是一個純粹的數(shù)據(jù)類——結(jié)構(gòu)體。于是你將其定義為:

struct PDU 
{
  time_t timestamp; 
  data_t today;
  int value;
};

隨后你發(fā)現(xiàn),在使用的過程中,總是需要重復(fù)這樣的代碼:

PDU pdu;

pdu.timestamp = now();
pdu.today     = today();
pdu.value     = value;

其中,前兩個字段的設(shè)置方式是確定的,都是取系統(tǒng)的當前時間和日期。作為一個專業(yè)程序員,這種重復(fù)讓你無法忍受,所以你決定實現(xiàn)一個類似于C語言的初始化函數(shù):

void init_pdu(PDU& pdu, int value) 
{
  pdu.timestamp = now();
  pdu.today     = today();
  pdu.value     = value;
}

然后,你就可以這樣來調(diào)用:

PDU pdu;

init_pdu(pdu, 5);

這是一種典型的C語言處理手法。如果你是個更老練的C++程序員,會選擇使用構(gòu)造函數(shù):

struct PDU 
{
  explicit PDU(int value) 
    : timespace(::now())
    , today(::today())
    , value(value)
  {}
  
  time_t timestamp; 
  data_t today;
  int value;
};

這種做法沒有帶來任何副作用:沒有任何額外的內(nèi)存和性能開銷。甚至,嚴格來講,由于使用了初始化列表,它的性能還略微提高了。

除此之外,你還收獲了一些其它好處:

  • 它的代碼和數(shù)據(jù)被毫無爭議、無法分割的放在了一起,有著更好的內(nèi)聚性可理解性;
  • 具備某種強制性:你永遠也不可能忘記調(diào)用構(gòu)造函數(shù);
  • 客戶代碼更加的簡潔,直觀。比如:
PDU pdu(5);

基于這些理由,我們讓類型PDU有了一個行為。按照之前的規(guī)定,我們需要把它從struct改成class。這似乎不難做到,但問題在于:為什么我們需要關(guān)注這件事情?如果從一開始它就是一個class,哪怕它沒有任何行為,我們寶貴的時間和精力就不會被無謂的消耗。

這還不算完。當你將一個類型由struct改為class后,所有對其進行聲明的地方都需要做同步的修改。否則,編譯器將會發(fā)出警告。作為一個紀律嚴明的專業(yè)軟件公司的雇員,你被要求消除掉所有告警。于是,更多的精力被毫無價值的浪費了。

所以,以這種原則來區(qū)分struct還是class不會帶來任何好處,只會帶來一堆麻煩。于是,就不難得出這樣的結(jié)論:我們只應(yīng)該堅持使用其中一個。

問題是:哪一個?

接口定義時的差別

除了名字不同之外,classstruct唯一的差別是:默認可見性。這體現(xiàn)在定義時繼承時struct在定義一個成員,或者繼承時,如果不指明,則默認為public; 而class則默認為 private

但這不是我要討論的重點,介紹語言的基礎(chǔ)特性并不是本文的目標,重點是這樣的差別會產(chǎn)生出不同的代碼。

比如,現(xiàn)在要定義一個純虛類,用兩個不同的關(guān)鍵字,會導(dǎo)致如下不同的結(jié)果:

class Interface 
{
public:
  virtual int invoke() = 0;
  virtual ~Interface() {} 
};

struct Interface 
{
  virtual int invoke() = 0;
  virtual ~Interface() {} 
};

兩者差別很小,你或許并不在意。但對我而言,一個純虛類,從邏輯上本來就是一個只有公開方法聲明、沒有實現(xiàn)細節(jié)的接口類。它所聲明的一切都應(yīng)該是公開的。在這樣的契約關(guān)系下,如果再通過public指明其公開性,這屬于畫蛇添足。

懶惰的我討厭冗余,討厭重復(fù)。更何況從平衡和美感的角度看,那個橫立的public就像潔白墻面上的一沫蚊子血,顯得格外刺眼。

繼承時的差別

而這并非故事的全部。struct的默認公開性還體現(xiàn)在繼承時:像成員一樣,如果未指明,struct對于父類的繼承默認為public繼承。而class則恰恰相反。這個規(guī)則本身沒有問題,問題在于我們?nèi)绾芜x擇。

作為一個有經(jīng)驗的C++程序員,在至少百分之九十以上的情況下,都會使用公有繼承(對于很多C++程序員,這個比例是百分之百)。這就意味著,在絕大多數(shù)情況下(如果不是全部的話),我們都要一遍遍的書寫public——這不是一個理性的選擇。(Typing does take time, doesn't it?)

class Derived 
  : public Base1
  , public Base2
  , public Base3 
{ 
  // more code
};

而一旦我們換作使用struct來定義一個類,則所有不必要的 public聲明就自然省略:

struct Derived 
  : Base1
  , Base2
  , Base3 
{ 
  // more code
};

確實干凈多了,不是嗎?

實體類定義時的差別

實體類不同于接口類,往往存在私有數(shù)據(jù)(沒有數(shù)據(jù),只有實現(xiàn)的實體類也往往意味著壞味道,一些表現(xiàn)算法的策略類除外),而class默認私有性,讓這種場景成為它出彩的機會。

class Foo 
{ 
  int a; 
  double b;
  
public: 
  Foo(int);
  void doSomething(); 
};

這個類把私有數(shù)據(jù)定義在前面,把公開方法定義在后面,所以可以利用 class的默認私有性。

但這樣的定義布局并不只是順序上的差異。我們的認知習慣和閱讀順序決定了我們總是希望把更重要的、更希望人們了解的信息擺在一目了然的位置。而不是讓別人穿越重重迷霧才能找到自己的關(guān)注點。我們希望別人更容易理解我們的意圖,而不是試圖挑戰(zhàn)別人的智商和耐心。

所以,信息擺放的順序就成了一件有所謂的事情。你如果認為私有實現(xiàn)細節(jié)更為重要,那就把私有數(shù)據(jù)擺在前面。否則,就把公開方法置于前列。

對于把程序理解為“數(shù)據(jù)結(jié)構(gòu)+算法”的程序員,盡管正在使用面向?qū)ο蟮脑亍邦悺保瑓s依然會認為理解一個程序的前提是理解它的數(shù)據(jù)結(jié)構(gòu)。在這樣的價值驅(qū)動下,把私有數(shù)據(jù)擺在前面就是完全合情合理的。數(shù)年前,我就曾在一本C++相關(guān)的書中讀到過這樣的建議。

但對于越來越確信信息隱藏對軟件之重要的我,則更傾向于認為公開接口才是了解一個模塊最關(guān)鍵的知識。當試圖去使用一個模塊時,我總是會優(yōu)先查看它的測試用例(如果有的話),然后再去看它的公開接口聲明。一般而言,這對于理解它能做什么,以及如何使用它已經(jīng)足夠;接口聲明處的私有元素反而會干擾我對一個模塊的理解。只有在好奇心的驅(qū)使下,我才會進一步去看看它的實現(xiàn)。

基于這樣的認知,當定義一個類的時候,我會把公開方法定義在前面。至于私有的內(nèi)容,我總是會竭盡所能的不希望引起人們的注意,寧愿付出一些代價,也要把它們藏到別人在類聲明里看不到的地方,更不會放在前面擾亂視聽。

所以,我仍然會選擇struct來定義實體類。如下:

struct Foo 
{
  Foo(int);
  void doSomething();

private:
  int a;
  double b; 
};

保持一致

無論是接口,還是實現(xiàn)類;無論是一個類以行為為中心,還是以數(shù)據(jù)為中心,使用 struct 而不是 class 都會給你的編程帶來一定的便利。

基于某些原因——無論是遺留系統(tǒng)的阻力,還是個人偏好的牽引——在了解了這所有的一切之后,你可能仍然選擇使用class。這沒有太大問題,畢竟你的程序你做主。

但需要強調(diào)的是,無論你喜歡class還是struct,都應(yīng)該堅持選擇其中一個,而不是混合使用(比如不要在定義數(shù)據(jù)類的時候使用 struct,定義行為類的時候使用class)。否則,在大量使用前導(dǎo)聲明的情況下,一旦某個使用struct的類改為class,或反過來,所有的前導(dǎo)聲明都需要做相應(yīng)修改。或許編譯器并不認為這種不一致是一種錯誤,但那些不斷騷擾你的警告亦會讓你不勝其煩。

總結(jié)

為什么要以這么大的篇幅討論structclass?

首先,因為我所寫的所有C++相關(guān)文章都會使用struct來定義(在實際項目中也一直如此)。如果不在這里給出說明,一些人可能會感到困惑。

其次是因為此問題在社區(qū)內(nèi)尚無定論,很多團隊在給出structclass的選擇問題時,給出的選擇理由并不成立。一次深入的討論對于社區(qū)是有價值的。

最后,通過這樣的討論,我希望闡述的并非結(jié)論,而是其背后所遵守價值觀和原則。這會有助于理解我其它文章的內(nèi)容。無論話題如何變化,我都會遵守一致的價值觀和原則。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,778評論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,745評論 0 9
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,709評論 18 399
  • 援引自:https://blogs.mentor.com/colinwalls/blog/2014/06/02/s...
    PatrickHC閱讀 578評論 0 2
  • 說明 官方windows版本編譯文檔有點坑爹,依賴庫編譯都編譯不出來,在網(wǎng)上找了好久,終于找到一個編譯比特幣錢包的...
    Harlin_閱讀 925評論 1 1