為什麼這篇Class 繼承struct鄉民發文收入到精華區:因為在Class 繼承struct這個討論話題中,有許多相關的文章在討論,這篇最有參考價值!作者loveflames (咕啾咕啾魔法陣)看板C_and_CPP標題[心得] 關於設計class的...
class如何設計,這個題目太大了
沒辦法只用一篇文章交待完畢,下面只能提我想到的
(1)class vs struct vs union
union用途比較特殊,不再特別提
traits、metafunction、functor,使用struct
有不變式者使用class,否則用struct
補充:
這裡說明一下如何判斷有無不變式
當data成員可以各自任意獨立變更內容時,表示沒有不變式
例如一個類別有月、日欄位,哪些日合法要看月的值,有不變式
或是有一欄位表示元素數量,一欄位指向各元素存放位置,也有不變式
(2)3/5/0法則
除非必要,否則不要自己寫copy、move、dtor
如果寫了其中一個,通常也要寫其他四個成員函數
(3)obj成員 vs ptr成員 vs ref成員
先判斷採組合還是聚合,再判斷是否有降低類型相依性的需要
smart ptr,有所有權
raw ptr,盡量讓其沒有所有權(見補充)
ref,沒有所有權,而且不能改變值
盡量用std::function取代函數指標
補充:
C++ Core Guidelines R.3與R.4,建議讓raw ptr與ref沒有所有權
但raw ptr有例外,例如舊代碼,或是底層實作的需要
(4)static成員 vs non-static成員
成員基於類別,static成員
成員基於物件,non-static成員
(5)public權限 vs private權限 vs protected權限
data成員:
各成員之間有不變式關係,private權限
各成員之間無不變式關係,public權限
避免protected data
non-const data權限要一樣
成員函數:
提供給任意使用者的介面,public權限
僅提供給子類的介面,protected權限
只提供類別內部使用,private權限
(6)public繼承 vs private繼承 vs protected繼承
有"IS-A"或"-ABLE"關係,繼承介面(可能還有實作),public繼承
有"HAS-A"關係,僅繼承實作,private繼承
補充:
http://www.gotw.ca/publications/mill06.htm
private繼承跟protected繼承可以用在受控制的多型
這用途很罕見,所以protected繼承基本上可以忽略
(7)private繼承 vs 組合
在"HAS-A"關係的前提下考慮這個
優先用組合,除非以下情況(細節見Effective C++ Item 39)
為了節省empty class佔用的空間
需要用到protected成員
需要改寫父類的virtual函數,例如template pattern
(8)public繼承 vs 組合
https://isocpp.org/wiki/faq/multiple-inheritance
看上面連結的經驗法則1
主要目的是代碼重用時,組合
主要目的是多型時,繼承
補充:
記住,繼承的耦合度高於組合
繼承的主要目的不是代碼重用,盡量避免實作繼承
(9)virtual函數 vs non-virtual函數
讓子類只繼承介面,pure virtual函數
讓子類繼承介面跟預設實作,impure virtual函數
提供預設實作並強迫子類顯式呼叫,為pure virtual函數提供定義
讓子類繼承介面跟強制實作,non-virtual函數
讓子類只改寫方法的某些步驟,用NVI包裝virtual函數(template pattern)
多型類別必須有virtual dtor
補充:
記得預設參數是靜態綁定,不要試圖改寫
(10)operator overloading
除非必要,否則不要重寫
盡量避免重寫operator&&跟operator||,會失去短路求值特性
盡量避免重寫unary &,遇到incomplete type會導致結果無法預期
operator+以operator+=來實作,以此類推
operator+設計成非成員函數,operator+=設計成成員函數,以此類推
unary設計成成員函數,binary設計成非成員函數
postfix ++以prefix ++實作,以此類推
operator=不要設virtual
如果重寫類型轉換函數,請宣告成explicit,除非希望隱式轉換
(11)ctor
不要自己寫一個只初始data成員的default ctor
單參數ctor(不含copy跟move)宣告成explicit,除非希望隱式轉換
初始過程如果想要有虛函數的行為,請改用factory pattern
考慮是否需要用using繼承父類的ctor
(12)多型類別禁止public copy/move
如果想複製,請改用prototype pattern
(13)exception safety
dtor、swap、move、回收函數必須做到nothrow
default ctor、operator==(含其他比較運算子)盡量做到nothrow
(14)當函數需要直接存取內部成員時才作為成員函數
以下為例外情況:
虛函數
operator overloading另有一套判斷方式
重載集合不是每個函數都會直接存取內部成員
返回this的函數
(15)如果有作為成員函數的需要,再繼續判斷是否改用friend函數
https://isocpp.org/wiki/faq/friends
第一個函數參數不是該物件本身(見補充),friend函數
涉及binary運算,friend函數
其餘情況,優先用成員函數
補充:
應該是在講std::invoke
(16)friend函數 vs static成員函數 vs non-static成員函數
如果經過(14)跟(15)仍無法決定,可以繼續參考下面
如果無法用ADL找到該函數的聲明,不考慮friend函數
如果需要相當於this的參數,不考慮static成員函數
如果不要相當於this的參數,不考慮non-static成員函數
補充:
http://www.gotw.ca/publications/mill02.htm
非成員函數如果不滿足介面原則,就沒有設計成friend的意義
static成員函數如果需要this,就沒有不用non-static版本的理由
(17)const成員函數 vs non-const成員函數
除非確實需要修改data成員,否則選擇const成員函數
(18)動態多型 vs 靜態多型
優先使用動態多型,除非有用CRTP消除virtual函數開銷的必要
(19)函數的定義是否放在class body
為了降低編譯相依性或不要inline,不放
為了可讀性或inline,放
補充:
virtual函數不會inline,ctor跟dtor盡量不要inline
除非必要,否則不考慮函數是否inline
函數定義如果不放在class body,則定義應放在cpp檔
(20)多重繼承
https://isocpp.org/wiki/faq/multiple-inheritance
考慮bridge pattern跟nested generalization能否作為替代方案
補充:
使用多重繼承需要很多前置知識,謹慎使用
(21)虛擬繼承
虛擬繼承的主要用途是搭配多重繼承
盡量避免在virtual base放data
(22)如果需要限制繼承深度
優先考慮final
限制之後往下最多可以繼承幾層,虛擬繼承 + private ctor
(23)design pattern
視使用情境而定,例如
strategy,動態切換演算法
adapter,解決介面不相容的問題
singleton,確保一個類只有一個實例
(24)mixin
主要目的是代碼重用,由任意個元件組成一個新類別,有下面兩種方式實現
可變模板 + 多重繼承,每個基類皆為一個元件
模板 + 單繼承,每層皆為一個元件
補充:
每個元件功能盡可能單一化,且採public繼承
public繼承一般不是為了代碼重用,所以使用mixin前請先了解一般的繼承用法
多重繼承版本,mixin元件可以共享方法,但要轉型才能使用
單繼承版本,mixin元件只能單向共享內層的方法,不用轉型就能使用
(25)常數
執行期常數,const
編譯期常數 + 有使用具名enum的必要,enum class
編譯期常數 + 一般情況,constexpr
補充:
enum每個常數之間要有足夠的關聯,不能只用來包裝一堆無關常數
用enum class取代enum,可以防止隱式轉型及名稱污染
(26)類型成員
nested class:不要同時定義class及聲明該類型的變數
enum:不要同時定義enum及聲明該類型的變數
類型別名:用using取代typedef,因為前者可讀性高又能用在alias template
補充:
data成員、成員函數、類型成員應按性質各自集中擺放
故類型成員不宜跟data成員混在一起
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 36.237.69.224 (臺灣)
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1624780030.A.7B4.html