如何設(shè)計(jì)一個(gè) C 的類?
時(shí)間:2021-08-19 16:12:21
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]↓推薦關(guān)注↓什么是類?我理解類是現(xiàn)實(shí)世界的描述,是對(duì)業(yè)務(wù)的抽象,類設(shè)計(jì)的好不好多半取決于你抽象的巧不巧。類的設(shè)計(jì)最重要的一點(diǎn)是要表示來(lái)自某個(gè)領(lǐng)域的概念,拿我最近在做的音視頻剪輯來(lái)舉例,剪輯業(yè)務(wù)中有軌道的概念,也有片段的概念,每個(gè)軌道可包含多個(gè)片段,這時(shí)候就有些問(wèn)題需要考慮,在現(xiàn)實(shí)...
↓推薦關(guān)注↓ 什么是類?
我理解類是現(xiàn)實(shí)世界的描述,是對(duì)業(yè)務(wù)的抽象,類設(shè)計(jì)的好不好多半取決于你抽象的巧不巧。
類的設(shè)計(jì)最重要的一點(diǎn)是要表示來(lái)自某個(gè)領(lǐng)域的概念,拿我最近在做的音視頻剪輯來(lái)舉例,剪輯業(yè)務(wù)中有軌道的概念,也有片段的概念,每個(gè)軌道可包含多個(gè)片段,這時(shí)候就有些問(wèn)題需要考慮,在現(xiàn)實(shí)世界中,軌道可以復(fù)制嗎?片段可以復(fù)制嗎?軌道可以移動(dòng)嗎?片段可以移動(dòng)嗎?
然后我們就可以進(jìn)一步將現(xiàn)實(shí)世界中的軌道和片段抽象成類了,可分為兩個(gè)類,一個(gè)軌道類,一個(gè)片段類,兩個(gè)類是否需要提供拷貝構(gòu)造函數(shù)和移動(dòng)構(gòu)造函數(shù),完全取決于它們?cè)诂F(xiàn)實(shí)世界的樣子。
tips:類的名字應(yīng)該明確告訴用戶這個(gè)類的用途。
類需要自己寫構(gòu)造函數(shù)和析構(gòu)函數(shù)嗎?
反正我每次定義一個(gè)類的時(shí)候都會(huì)明確把構(gòu)造函數(shù)和析構(gòu)函數(shù)寫出來(lái),即便它是空實(shí)現(xiàn),即便我不寫編譯器也會(huì)視情況默認(rèn)生成一個(gè),自動(dòng)生成的稱為默認(rèn)構(gòu)造函數(shù)。但我不想依賴編譯器,也建議大家不要過(guò)度依賴編譯器,明確寫出來(lái)構(gòu)造函數(shù)和析構(gòu)函數(shù)也是一個(gè)好習(xí)慣,多數(shù)情況下類沒(méi)有那么簡(jiǎn)單,多數(shù)情況下編譯器默認(rèn)生成的構(gòu)造函數(shù)和析構(gòu)函數(shù)不一定是我們想要的。默認(rèn)的構(gòu)造函數(shù)不會(huì)給我們的數(shù)據(jù)成員初始化,所以需要自己寫一個(gè)構(gòu)造函數(shù),其實(shí)在構(gòu)造函數(shù)里的語(yǔ)句也不能稱之為初始化,那是個(gè)賦值操作,真正的初始化可以通過(guò)初始化列表方式或者聲明成員時(shí)直接給初值,類似下面的代碼。如果我們的類有指針數(shù)據(jù)成員,我們?cè)谀硞€(gè)地方為其分配了一塊內(nèi)存,編譯器自動(dòng)生成的析構(gòu)函數(shù)默認(rèn)是不會(huì)將這塊內(nèi)存釋放掉的,為了規(guī)避這潛在的風(fēng)險(xiǎn),還是自己寫一個(gè)吧!
tips:編譯器在某些情況下會(huì)生成移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符,但記住這些情況太麻煩了,建議手動(dòng)控制,明確要的時(shí)候就自己寫一個(gè),明確不要的時(shí)候就delete掉。
類需要手動(dòng)聲明默認(rèn)構(gòu)造函數(shù)嗎?
什么是默認(rèn)構(gòu)造函數(shù)?看下百度百科的定義:
默認(rèn)構(gòu)造函數(shù)(default constructor)就是在沒(méi)有顯式提供初始化式時(shí)調(diào)用的構(gòu)造函數(shù)。它由不帶參數(shù)的構(gòu)造函數(shù),或者為所有的形參提供默認(rèn)實(shí)參的構(gòu)造函數(shù)定義。如果定義某個(gè)類的變量時(shí)沒(méi)有提供初始化時(shí)就會(huì)使用默認(rèn)構(gòu)造函數(shù)。
這和上一個(gè)問(wèn)題類似,首先需要了解什么時(shí)候需要默認(rèn)構(gòu)造函數(shù),看下面這段代碼。當(dāng)已經(jīng)為一個(gè)類提供了帶有參數(shù)的構(gòu)造函數(shù),編譯器不會(huì)為該類再默認(rèn)的生成構(gòu)造函數(shù),如果此時(shí)在其它地方以無(wú)參形式構(gòu)造了該類的一個(gè)對(duì)象,編譯器就會(huì)報(bào)錯(cuò),找不到對(duì)應(yīng)的構(gòu)造函數(shù),那怎么解決?一種方法是為類設(shè)置一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù)(像下面代碼這樣),另一種方法是自己提供一個(gè)對(duì)應(yīng)的構(gòu)造函數(shù)。我傾向于后一種方式,前一種方式只能解決編譯上的問(wèn)題,但還有可能存在潛在的bug。
數(shù)據(jù)成員是設(shè)置private還是public還是protected?三種訪問(wèn)權(quán)限就不過(guò)多介紹了,說(shuō)說(shuō)我平時(shí)是怎么設(shè)置數(shù)據(jù)成員權(quán)限的吧!對(duì)于普通成員變量,我全是private,除非該類作為基類,而子類也需要訪問(wèn)父類的私有成員,這時(shí)候我會(huì)將父類的private改為protected。什么時(shí)候用public呢?一般情況下只會(huì)對(duì)某些靜態(tài)常量我會(huì)考慮使用public修飾,前提是外部有訪問(wèn)此常量的需求。
類需要虛析構(gòu)函數(shù)嗎?
這個(gè)很明確,如果類會(huì)作為基類被派生時(shí),該基類的析構(gòu)函數(shù)就一定要聲明為虛函數(shù),如果某個(gè)類確定不會(huì)被派生,那就不要聲明其析構(gòu)函數(shù)為虛函數(shù)。
類需要提供拷貝構(gòu)造函數(shù)嗎?
這里需要考慮清楚,需要明確究竟是否提供,這需要結(jié)合這個(gè)類在現(xiàn)實(shí)生活中的實(shí)際意義,類是某個(gè)領(lǐng)域某個(gè)業(yè)務(wù)某個(gè)實(shí)物的抽象,假設(shè)有一個(gè)試卷類,因?yàn)樵嚲砜梢钥截?,那就明確提供拷貝構(gòu)造函數(shù),假設(shè)有一個(gè)Person類,因?yàn)椴辉试S克隆人,那就明確禁用拷貝構(gòu)造函數(shù)。這里也可以參考智能指針中的unique_ptr,該智能指針就明確禁用了拷貝操作。
類需要提供移動(dòng)構(gòu)造函數(shù)嗎?
移動(dòng)構(gòu)造是C 11引入的新特性,這里涉及到左值右值等概念。
一個(gè)類具有移動(dòng)構(gòu)造函數(shù)才具備移動(dòng)語(yǔ)義,如果追求資源管理的效率,move資源效率一般會(huì)比拷貝一個(gè)資源高一些。
這里重點(diǎn)討論是否需要提供移動(dòng)構(gòu)造函數(shù),答案還是,要想清楚,要結(jié)合實(shí)際情況,假設(shè)我們定義了一個(gè)美國(guó)總統(tǒng)的類,可以提供移動(dòng)構(gòu)造函數(shù),因?yàn)槊绹?guó)總統(tǒng)幾年就會(huì)換一個(gè),再假設(shè)我們定義了一個(gè)美國(guó)最傻吊總統(tǒng)的類,那就應(yīng)該禁用移動(dòng)構(gòu)造函數(shù),因?yàn)橹挥卸跻粋€(gè),永遠(yuǎn)不可移動(dòng)。
排坑:賦值運(yùn)算符需要考慮是否能正確的防止自身給自身賦值?
我理解類是現(xiàn)實(shí)世界的描述,是對(duì)業(yè)務(wù)的抽象,類設(shè)計(jì)的好不好多半取決于你抽象的巧不巧。
類的設(shè)計(jì)最重要的一點(diǎn)是要表示來(lái)自某個(gè)領(lǐng)域的概念,拿我最近在做的音視頻剪輯來(lái)舉例,剪輯業(yè)務(wù)中有軌道的概念,也有片段的概念,每個(gè)軌道可包含多個(gè)片段,這時(shí)候就有些問(wèn)題需要考慮,在現(xiàn)實(shí)世界中,軌道可以復(fù)制嗎?片段可以復(fù)制嗎?軌道可以移動(dòng)嗎?片段可以移動(dòng)嗎?
然后我們就可以進(jìn)一步將現(xiàn)實(shí)世界中的軌道和片段抽象成類了,可分為兩個(gè)類,一個(gè)軌道類,一個(gè)片段類,兩個(gè)類是否需要提供拷貝構(gòu)造函數(shù)和移動(dòng)構(gòu)造函數(shù),完全取決于它們?cè)诂F(xiàn)實(shí)世界的樣子。
tips:類的名字應(yīng)該明確告訴用戶這個(gè)類的用途。
類需要自己寫構(gòu)造函數(shù)和析構(gòu)函數(shù)嗎?
反正我每次定義一個(gè)類的時(shí)候都會(huì)明確把構(gòu)造函數(shù)和析構(gòu)函數(shù)寫出來(lái),即便它是空實(shí)現(xiàn),即便我不寫編譯器也會(huì)視情況默認(rèn)生成一個(gè),自動(dòng)生成的稱為默認(rèn)構(gòu)造函數(shù)。但我不想依賴編譯器,也建議大家不要過(guò)度依賴編譯器,明確寫出來(lái)構(gòu)造函數(shù)和析構(gòu)函數(shù)也是一個(gè)好習(xí)慣,多數(shù)情況下類沒(méi)有那么簡(jiǎn)單,多數(shù)情況下編譯器默認(rèn)生成的構(gòu)造函數(shù)和析構(gòu)函數(shù)不一定是我們想要的。默認(rèn)的構(gòu)造函數(shù)不會(huì)給我們的數(shù)據(jù)成員初始化,所以需要自己寫一個(gè)構(gòu)造函數(shù),其實(shí)在構(gòu)造函數(shù)里的語(yǔ)句也不能稱之為初始化,那是個(gè)賦值操作,真正的初始化可以通過(guò)初始化列表方式或者聲明成員時(shí)直接給初值,類似下面的代碼。如果我們的類有指針數(shù)據(jù)成員,我們?cè)谀硞€(gè)地方為其分配了一塊內(nèi)存,編譯器自動(dòng)生成的析構(gòu)函數(shù)默認(rèn)是不會(huì)將這塊內(nèi)存釋放掉的,為了規(guī)避這潛在的風(fēng)險(xiǎn),還是自己寫一個(gè)吧!
tips:編譯器在某些情況下會(huì)生成移動(dòng)構(gòu)造函數(shù)或移動(dòng)賦值運(yùn)算符,但記住這些情況太麻煩了,建議手動(dòng)控制,明確要的時(shí)候就自己寫一個(gè),明確不要的時(shí)候就delete掉。
class A {
public:
A() : a_(2) {}// 一種初始化,標(biāo)準(zhǔn)初始化形式
~A() {}
private:
int a_;
int b_ = 3; // 另一種初始化
};
類需要手動(dòng)聲明默認(rèn)構(gòu)造函數(shù)嗎?
什么是默認(rèn)構(gòu)造函數(shù)?看下百度百科的定義:
默認(rèn)構(gòu)造函數(shù)(default constructor)就是在沒(méi)有顯式提供初始化式時(shí)調(diào)用的構(gòu)造函數(shù)。它由不帶參數(shù)的構(gòu)造函數(shù),或者為所有的形參提供默認(rèn)實(shí)參的構(gòu)造函數(shù)定義。如果定義某個(gè)類的變量時(shí)沒(méi)有提供初始化時(shí)就會(huì)使用默認(rèn)構(gòu)造函數(shù)。
這和上一個(gè)問(wèn)題類似,首先需要了解什么時(shí)候需要默認(rèn)構(gòu)造函數(shù),看下面這段代碼。當(dāng)已經(jīng)為一個(gè)類提供了帶有參數(shù)的構(gòu)造函數(shù),編譯器不會(huì)為該類再默認(rèn)的生成構(gòu)造函數(shù),如果此時(shí)在其它地方以無(wú)參形式構(gòu)造了該類的一個(gè)對(duì)象,編譯器就會(huì)報(bào)錯(cuò),找不到對(duì)應(yīng)的構(gòu)造函數(shù),那怎么解決?一種方法是為類設(shè)置一個(gè)無(wú)參的默認(rèn)構(gòu)造函數(shù)(像下面代碼這樣),另一種方法是自己提供一個(gè)對(duì)應(yīng)的構(gòu)造函數(shù)。我傾向于后一種方式,前一種方式只能解決編譯上的問(wèn)題,但還有可能存在潛在的bug。
class A {
A(int a) {}
A() = default;
};
數(shù)據(jù)成員是設(shè)置private還是public還是protected?三種訪問(wèn)權(quán)限就不過(guò)多介紹了,說(shuō)說(shuō)我平時(shí)是怎么設(shè)置數(shù)據(jù)成員權(quán)限的吧!對(duì)于普通成員變量,我全是private,除非該類作為基類,而子類也需要訪問(wèn)父類的私有成員,這時(shí)候我會(huì)將父類的private改為protected。什么時(shí)候用public呢?一般情況下只會(huì)對(duì)某些靜態(tài)常量我會(huì)考慮使用public修飾,前提是外部有訪問(wèn)此常量的需求。
class A {
public:
constexpr static int kConstValue = 2;
private:
int a_;
};
類需要虛析構(gòu)函數(shù)嗎?
這個(gè)很明確,如果類會(huì)作為基類被派生時(shí),該基類的析構(gòu)函數(shù)就一定要聲明為虛函數(shù),如果某個(gè)類確定不會(huì)被派生,那就不要聲明其析構(gòu)函數(shù)為虛函數(shù)。
類需要提供拷貝構(gòu)造函數(shù)嗎?
這里需要考慮清楚,需要明確究竟是否提供,這需要結(jié)合這個(gè)類在現(xiàn)實(shí)生活中的實(shí)際意義,類是某個(gè)領(lǐng)域某個(gè)業(yè)務(wù)某個(gè)實(shí)物的抽象,假設(shè)有一個(gè)試卷類,因?yàn)樵嚲砜梢钥截?,那就明確提供拷貝構(gòu)造函數(shù),假設(shè)有一個(gè)Person類,因?yàn)椴辉试S克隆人,那就明確禁用拷貝構(gòu)造函數(shù)。這里也可以參考智能指針中的unique_ptr,該智能指針就明確禁用了拷貝操作。
類需要提供移動(dòng)構(gòu)造函數(shù)嗎?
移動(dòng)構(gòu)造是C 11引入的新特性,這里涉及到左值右值等概念。
一個(gè)類具有移動(dòng)構(gòu)造函數(shù)才具備移動(dòng)語(yǔ)義,如果追求資源管理的效率,move資源效率一般會(huì)比拷貝一個(gè)資源高一些。
這里重點(diǎn)討論是否需要提供移動(dòng)構(gòu)造函數(shù),答案還是,要想清楚,要結(jié)合實(shí)際情況,假設(shè)我們定義了一個(gè)美國(guó)總統(tǒng)的類,可以提供移動(dòng)構(gòu)造函數(shù),因?yàn)槊绹?guó)總統(tǒng)幾年就會(huì)換一個(gè),再假設(shè)我們定義了一個(gè)美國(guó)最傻吊總統(tǒng)的類,那就應(yīng)該禁用移動(dòng)構(gòu)造函數(shù),因?yàn)橹挥卸跻粋€(gè),永遠(yuǎn)不可移動(dòng)。
排坑:賦值運(yùn)算符需要考慮是否能正確的防止自身給自身賦值?
class?A?{
???public:
????A();
????A(const?A