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