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缝左,畢竟它更直觀亿遂、準(zhǔn)確。

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

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

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

比如饭豹,最初的時候你定義了這樣一個數(shù)據(jù)結(jié)構(gòu)鸵赖,由于它是進(jìn)程間通信的消息包,所以在你看來拄衰,它應(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)的當(dā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)存和性能開銷祥楣。甚至开财,嚴(yán)格來講,由于使用了初始化列表误褪,它的性能還略微提高了床未。

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

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

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

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

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

問題是:哪一個?

接口定義時的差別

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

但這不是我要討論的重點灶平,介紹語言的基礎(chǔ)特性并不是本文的目標(biāo),重點是這樣的差別會產(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)細(xì)節(jié)的接口類。它所聲明的一切都應(yīng)該是公開的禀横。在這樣的契約關(guān)系下屁药,如果再通過public指明其公開性,這屬于畫蛇添足柏锄。

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

繼承時的差別

而這并非故事的全部茫舶。struct的默認(rèn)公開性還體現(xiàn)在繼承時:像成員一樣械巡,如果未指明刹淌,struct對于父類的繼承默認(rèn)為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默認(rèn)私有性女轿,讓這種場景成為它出彩的機(jī)會箭启。

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

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

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

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

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

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

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

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

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

private:
  int a;
  double b; 
};

保持一致

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

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

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

總結(jié)

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甚颂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌振诬,老刑警劉巖蹭睡,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赶么,居然都是意外死亡肩豁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門辫呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蓖救,“玉大人,你說我怎么就攤上這事印屁⊙啵” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵雄人,是天一觀的道長从橘。 經(jīng)常有香客問我,道長础钠,這世上最難降的妖魔是什么恰力? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旗吁,結(jié)果婚禮上踩萎,老公的妹妹穿的比我還像新娘。我一直安慰自己很钓,他們只是感情好香府,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著码倦,像睡著了一般企孩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袁稽,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天勿璃,我揣著相機(jī)與錄音,去河邊找鬼推汽。 笑死补疑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歹撒。 我是一名探鬼主播莲组,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栈妆!你這毒婦竟也來了胁编?” 一聲冷哼從身側(cè)響起厢钧,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嬉橙,沒想到半個月后早直,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡市框,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年霞扬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枫振。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡喻圃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粪滤,到底是詐尸還是另有隱情斧拍,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布杖小,位于F島的核電站肆汹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏予权。R本人自食惡果不足惜昂勉,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扫腺。 院中可真熱鬧岗照,春花似錦、人聲如沸笆环。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咧织。三九已至嗓袱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間习绢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工蝙昙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留闪萄,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓奇颠,卻偏偏與公主長得像败去,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烈拒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

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