https://github.com/lynnboy/CppCoreGuidelines-zh-CN/blob/master/CppCoreGuidelines-zh-CN.md
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md
P: 理念本章節中的規則都非常具有一般性。
理念性規則概覽:
P.1: 在程式碼中直接表達你的想法P.2: 用 ISO 標準 C++ 來編碼P.3: 表達你的設計意圖P.4: 理想情況下,程式應當是靜態型別安全的P.5: 編譯期檢查優先於執行時檢查P.6: 應當使無法在編譯期進行的檢查能夠在執行時實施P.7: 儘早識別執行時錯誤P.8: 不要洩漏任何資源P.9: 不要浪費時間或空間P.10: 不可變資料優先於可變資料P.11: 把雜亂的構造封裝起來,而別讓其散佈到程式碼中P.12: 適當採用支援工具P.13: 適當採用支援程式庫Philosophy rules summary:
P.1: Express ideas directly in codeP.2: Write in ISO Standard C++P.3: Express intentP.4: Ideally, a program should be statically type safeP.5: Prefer compile-time checking to run-time checkingP.6: What cannot be checked at compile time should be checkable at run timeP.7: Catch run-time errors earlyP.8: Don't leak any resourcesP.9: Don't waste time or spaceP.10: Prefer immutable data to mutable dataP.11: Encapsulate messy constructs, rather than spreading through the codeP.12: Use supporting tools as appropriateP.13: Use support libraries as appropriate: 介面介面是程式中的兩個部分之間的契約。嚴格地規定服務提供者和該服務使用者的預期是必要的。 在程式碼的組織中,良好的介面(易於理解,促進高效的使用方式,不易出錯,支援進行測試,等等)可能是最重要的單個方面了。
介面規則概覽:
I.1: 使介面明確I.2: 避免非 const 全域性變數I.3: 避免使用單例I.4: 使介面嚴格和強型別化I.5: 說明前條件(如果有)I.6: 優先使用 Expects() 來表達前條件I.7: 說明後條件I.8: 優先使用 Ensures() 來表達後條件I.9: 當介面是模板時,用概念來文件化其引數I.10: 使用異常來表明無法實施所要求的任務I.11: 決不以原始指標(T*)或引用(T&)來傳遞所有權I.12: 把不能為空的指標宣告為 not_nullI.13: 不要只用一個指標來傳遞陣列I.22: 避免全域性物件之間進行復雜的初始化I.23: 保持較少的函式引數數量I.24: 避免可以由同一組實參以不同順序呼叫造成不同含義的相鄰形參I.25: 優先以空抽象類作為類層次的介面I.26: 當想要跨編譯器的 ABI 時,使用一個 C 風格的語言子集I.27: 對於穩定的程式庫 ABI,考慮使用 Pimpl 手法I.30: 將有違規則的部分封裝InterfacesAn interface is a contract between two parts of a program. Precisely stating what is expected of a supplier of a service and a user of that service is essential. Having good (easy-to-understand, encouraging efficient use, not error-prone, supporting testing, etc.) interfaces is probably the most important single aspect of code organization.
Interface rule summary:
I.1: Make interfaces explicitI.2: Avoid non-const global variablesI.3: Avoid singletonsI.4: Make interfaces precisely and strongly typedI.5: State preconditions (if any)I.6: Prefer Expects() for expressing preconditionsI.7: State postconditionsI.8: Prefer Ensures() for expressing postconditionsI.9: If an interface is a template, document its parameters using conceptsI.10: Use exceptions to signal a failure to perform a required taskI.11: Never transfer ownership by a raw pointer (T*) or reference (T&)I.12: Declare a pointer that must not be null as not_nullI.13: Do not pass an array as a single pointerI.22: Avoid complex initialization of global objectsI.23: Keep the number of function arguments lowI.24: Avoid adjacent parameters that can be invoked by the same arguments in either order with different meaningI.25: Prefer empty abstract classes as interfaces to class hierarchiesI.26: If you want a cross-compiler ABI, use a C-style subsetI.27: For stable library ABI, consider the Pimpl idiomI.30: Encapsulate rule violationsSee also:
F: FunctionsC.concrete: Concrete typesC.hier: Class hierarchiesC.over: Overloading and overloaded operatorsC.con: Containers and other resource handlesE: Error handlingT: Templates and generic programmingT: 模板和泛型程式設計泛型程式設計是使用以型別、值和演算法進行引數化的型別和演算法所進行的程式設計。 在 C++ 中,泛型程式設計是以“模板”語言機制來提供支援的。
泛型函式的引數是以針對引數型別及其所涉及的值的規定的集合來刻畫的。 在 C++ 中,這些規定是以被稱為概念(concept)的編譯期謂詞來表達的。
模板還可用於進行超程式設計;亦即由編譯期程式碼所組成的程式。
泛型程式設計的中心是“概念”;亦即以編譯時謂詞表現的對於模板引數的要求。 “概念”是在一份 ISO 技術規範中定義的:concepts。 而一組標準庫概念的草案則可以在另一份 ISO TS 中找到:ranges。 GCC 6.1 及其後版本支援概念。 因此,我們在例子中將概念註釋掉了;就是說我們僅把它們當成形式化的註釋。 如果你使用 GCC 6.1 或更新版本,那麼你就可以取消它們的註釋。
模板使用的規則概覽:
T.1: 用模板來提升程式碼的抽象層次T.2: 用模板來表達適用於許多引數型別的演算法T.3: 用模板來表達容器和範圍T.4: 用模板來表達對語法樹的操作T.5: 結合泛型和麵向物件技術來增強它們的能力,而不是它們的成本概念使用的規則概覽:
T.10: 為所有模板實參指明概念T.11: 儘可能採用標準概念T.12: 對於區域性變數,優先採用概念名而不是 autoT.13: 對於簡單的單型別引數概念,優先採用簡寫形式???概念定義的規則概覽:
T.20: 避免沒有有意義的語義的“概念”T.21: 為概念提出一組完整的操作要求T.22: 為概念指明公理T.23: 透過新增新的使用模式,從更一般情形的概念中區分出提煉後的概念T.24: 用標籤類或特徵類來區分僅在語義上存在差別的概念T.25: 避免互補性的約束T.26: 優先採用使用模式而不是簡單的語法來定義概念T.30: 節制地採用概念求反(!C<T>)來表示微小差異T.31: 節制地採用概念析取(disjunction)(C1<T> || C2<T>)來表示備選項???模板介面的規則概覽:
T.40: 使用函式物件向演算法傳遞操作T.41: 在模板的概念上僅提出基本的性質要求T.42: 使用模板別名來簡化寫法並隱藏實現細節T.43: 優先使用 using 而不是 typedef 來定義別名T.44: (如果可行)使用函式模板來對類模板的引數型別進行推斷T.46: 要求模板引數至少為 Regular 或者 SemiRegularT.47: 避免用常用名字命名高度可見的無約束模板T.48: 如果你的編譯器不支援概念的話,可以用 enable_if 來模擬T.49: 儘可能避免型別擦除模板定義的規則概覽:
T.60: 最小化模板的上下文依賴性T.61: 請勿對成員進行過度引數化(恐怖)T.62: 將無依賴的類模板成員置於一個非模板基類之中T.64: 用特化來提供類模板的其他實現T.65: 用標籤分派來提供函式的其他實現T.67: 用特化來提供不規則型別的其他實現T.68: 在模板中用 {} 而不是 () 以避免歧義T.69: 在模板中,請勿進行未限定的非成員函式呼叫,除非有意將之作為定製點模板和型別層次的規則概覽:
T.80: 請勿不成熟地對類層次進行模板化T.81: 請勿混合類層次和陣列 // ??? 放在“型別層次”部分T.82: 當不想要虛擬函式時,可以將類層次線性化T.83: 請勿宣告虛的成員函式模板T.84: 使用非模板的核心實現來提供 ABI 穩定的介面T.??: ????變參模板的規則概覽:
T.100: 當需要可以接受可變數量的多種型別引數的函式時,使用變參模板T.101: ??? 如何向變參模板傳遞引數 ???T.102: ??? 如何處理變參模板的引數 ???T.103: 請勿對同質引數列表使用變參模板T.??: ????超程式設計的規則概覽:
T.120: 僅當確實需要時才使用模板超程式設計T.121: 模板超程式設計主要用於模擬概念機制T.122: 用模板(通常為模板別名)來在編譯期進行型別運算T.123: 用 constexpr 函式來在編譯期進行值運算T.124: 優先使用標準庫的模板超程式設計設施T.125: 當需要標準庫之外的模板超程式設計設施時,使用某個現存程式庫T.??: ????其他模板規則概覽:
T.140: 對所有的可能會被重用的操作命名T.141: 當僅在一個地方需要一個簡單的函式物件時,使用無名的 lambdaT.142: 使用模板變數以簡化寫法T.143: 請勿編寫並非有意非泛型的程式碼T.144: 請勿特化函式模板T.150: 用 static_assert 來檢查類是否與概念相符T.??: ????NL: 命名和程式碼佈局規則維持一致的命名和程式碼佈局是很有用的。 即便不為其他原因,也可以減少“我的程式碼風格比你的好”這類的紛爭。 然而,人們使用許多許多的不同程式碼風格,並狂熱地堅持它們(的優缺點)。 而且,大多數的現實專案都包含來自於許多來源的程式碼,因而通常不可能把所有的程式碼都標準化為某個單一的程式碼風格。 經過許多的使用者請求給予指導後,我們給出一組規則,當你沒有更好的選擇時可以使用它們,但真正的目標在於一致性,而不是任何一組特定的規則。 IDE 和工具可以提供輔助(當然也可能造成妨礙)。
命名和程式碼佈局規則:
NL.1: 不要在程式碼註釋中說明可以由程式碼來清晰表達的東西NL.2: 在程式碼註釋中說明意圖NL.3: 保持程式碼註釋簡明乾脆NL.4: 保持一種統一的縮排風格NL.5: 避免在名字中編碼型別資訊NL.7: 使名字的長度大約正比於其作用域的長度NL.8: 使用一種統一的命名風格NL.9: 將 ALL_CAPS(全大寫)僅用於宏的名字NL.10: 優先採用 underscore_style(下劃線風格)的名字NL.11: 使字面量可閱讀NL.15: 節制地使用空格NL.16: 使用一種常規的類成員宣告次序NL.17: 使用從 K&R 衍生出的程式碼佈局NL.18: 使用 C++ 風格的宣告符佈局NL.19: 避免使用容易誤讀的名字NL.20: 不要把兩個語句放在同一行中NL.21: 每個宣告式(僅)宣告一個名字NL.25: 請勿將 void 用作引數型別NL.26: 採用符合慣例的 const 寫法F: 函式函式指定了一個活動或者一次計算,以將系統從一種一致的狀態轉移到另一種一致的狀態。函式是程式的基礎構造塊。
應當使函式的名字有意義,說明對其引數的必要條件,並清晰地規定引數和其結果之間的關係。函式的實現本身並不是規格說明。請嘗試同時對函式應當做什麼和函式應當怎樣做來進行思考。 函式在大多數介面中都是最關鍵的部分,請參考介面的規則。
函式規則概覽:
函式定義式的規則:
F.1: 把有意義的操作“打包”成為精心命名的函式F.2: 一個函式應當實施單一一項邏輯操作F.3: 保持函式短小簡潔F.4: 如果函式可能必須在編譯期進行求值,就將其宣告為 constexprF.5: 如果函式非常小,並且是時間敏感的,就將其宣告為 inlineF.6: 如果函式必然不會丟擲異常,就將其宣告為 noexceptF.7: 對於常規用法,應當接受 T* 或 T& 引數而不是智慧指標F.8: 優先採用純函式F.9: 未使用的形參應當沒有名字引數傳遞表示式的規則:
F.15: 優先採用簡單的和傳統的資訊傳遞方式F.16: 對於“輸入(in)”引數,把複製操作廉價的型別按值進行傳遞,把其他型別按 const 引用進行傳遞F.17: 對於“輸入/輸出(in-out)”引數,按非 const 引用進行傳遞F.18: 對於“將被移動(will-move-from)”引數,按 X&& 進行傳遞並對引數 std::moveF.19: 對於“轉發(forward)”引數,按 TP&& 進行傳遞並只對引數 std::forwardF.20: 對於“輸出(out)”值,採用返回值優先於輸出引數F.21: 要返回多個“輸出”值,優先返回結構體或元組(tuple)F.60: 當“沒有引數”是有效的選項時,採用 T* 優先於 T&引數傳遞語義的規則:
F.22: 用 T* 或 owner<T*> 來代表單個物件F.23: 用 not_null<T> 來表明“空值(null)”不是有效的值F.24: 用 span<T> 或者 span_p<T> 來代表一個半開序列F.25: 用 zstring 或者 not_null<zstring> 來代表 C 風格的字串F.26: 當需要指標時,用 unique_ptr<T> 來傳遞所有權F.27: 用 shared_ptr<T> 來共享所有權值返回語義的規則:
F.42: 返回 T* 來(僅僅)給出一個位置F.43: 不要(直接或間接)返回指向區域性物件的指標或引用F.44: 當不想進行復制,且不需要“沒有物件被返回”時,返回 T&F.45: 不要返回 T&&F.46: int 是 main() 的返回型別F.47: 賦值運算子返回 T&F.48: 不要用 return std::move(local)其他函式規則:
F.50: 當函式不適用時(不能俘獲區域性變數,或者不能編寫區域性函式),就使用 LambdaF.51: 如果需要作出選擇,採用預設實參應當優先於進行過載F.52: 對於區域性使用的(也包括傳遞給演算法的)lambda,優先採用按引用俘獲F.53: 對於非區域性使用的(包括被返回的,在堆上儲存的,或者傳遞給別的執行緒的)lambda,避免採用按引用俘獲F.54: 當俘獲了 this 時,顯式俘獲所有的變數(不使用預設俘獲)F.55: 不要使用 va_arg 引數函式和 Lambda 表示式以及函式物件有很強的相似性。
參見:C.lambdas: 函式物件和 lambda
函式佔用類的大小嗎?
https://maimai.cn/web/gossip_detail?gid=28548835&egid=f9572a925d4d11ebbb08801844e2d86c
虛擬函式:要佔用4個位元組,用來指定虛擬函式的虛擬函式表的入口地址。
C.ctor: 建構函式,賦值,和解構函式這些函式控制物件的生存期:建立,複製,移動,以及銷燬。 定義建構函式是為了確保以及簡化類的初始化過程。
以下被稱為預設操作:
預設建構函式: X()複製建構函式: X(const X&)複製賦值: operator=(const X&)移動建構函式: X(X&&)移動賦值: operator=(X&&)解構函式: ~X()預設情況下,編譯器會為這些操作中被使用的進行定義,但這些預設定義可以被抑制掉。
預設操作是一組互相關聯的操作,它們共同實現了物件的生存期語義。 預設情況下,C++ 按照值型別的方式來對待各個類,但並非所有的型別都與值型別相符。
預設操作的規則集合:
C.20: 只要可能,請避免定義任何的預設操作C.21: 如果定義或者 =delete 了任何複製、移動或解構函式,請定義或者 =delete 它們全部C.22: 使預設操作之間保持一致解構函式的規則:
C.30: 如果一個類需要在物件銷燬時執行明確的操作,請為其定義解構函式C.31: 類所獲取的所有資源,必須都在類的解構函式中進行釋放C.32: 如果類中帶有原始指標(T*)或者引用(T&),請考慮它是否是所有者C.33: 如果類中帶有所有權的指標成員,請定義解構函式C.35: 基類的解構函式應當要麼是 public 和 virtual,要麼是 protected 且非 virtualC.36: 解構函式不能失敗C.37: 使解構函式 noexcept建構函式的規則:
C.40: 如果類具有不變式,請為其定義建構函式C.41: 建構函式應當建立經過完整初始化的物件C.42: 當建構函式無法構造有效物件時,應當丟擲異常C.43: 保證可複製(值型別)類帶有預設建構函式C.44: 儘量讓預設建構函式簡單且不丟擲異常C.45: 不要定義僅對資料成員進行初始化的預設建構函式;應當使用成員初始化式C.46: 預設情況下,把單引數的建構函式宣告為 explicitC.47: 按成員宣告的順序對成員變數進行定義和初始化C.48: 對於常量初始化式來說,優先採用類中的初始化式而不是建構函式中的成員初始化式C.49: 優先進行初始化而不是在建構函式中賦值C.50: 當初始化過程中需要體現“虛擬函式行為”時,請使用工廠函式C.51: 用委派建構函式來表示類中所有建構函式的共同行為C.52: 使用繼承建構函式來把建構函式引入到無須進行其他的明確初始化操作的派生類之中複製和移動的規則:
C.60: 使複製賦值非 virtual,接受 const& 的引數,並返回非 const 的引用C.61: 複製操作應當進行復制C.62: 使複製賦值可以安全進行自賦值C.63: 使移動賦值非 virtual,接受 && 的引數,並返回非 const 的引用C.64: 移動操作應當進行移動,並使原物件處於有效狀態C.65: 使移動賦值可以安全進行自賦值C.66: 使移動操作 noexceptC.67: 多型類應當抑制複製操作其他的預設操作規則:
C.80: 當需要明確使用預設語義時,使用 =defaultC.81: 當需要關閉預設行為(且不需要替代的行為)時,使用 =deleteC.82: 不要在建構函式和解構函式中呼叫虛擬函式C.83: 考慮為值型別提供 noexcept 的 swap 函式C.84: swap 不能失敗C.85: 使 swap 函式 noexceptC.86: 使 == 對運算元的型別對稱,並使之 noexceptC.87: 請當心基類的 ==C.89: 使 hash 函式 noexceptC.90: 依靠建構函式和賦值運算子,不要依靠 memset 和 memcpyC: Classes and class hierarchiesA class is a user-defined type, for which a programmer can define the representation, operations, and interfaces. Class hierarchies are used to organize related classes into hierarchical structures.
Class rule summary:
C.1: Organize related data into structures (structs or classes)C.2: Use class if the class has an invariant; use struct if the data members can vary independentlyC.3: Represent the distinction between an interface and an implementation using a classC.4: Make a function a member only if it needs direct access to the representation of a classC.5: Place helper functions in the same namespace as the class they supportC.7: Don't define a class or enum and declare a variable of its type in the same statementC.8: Use class rather than struct if any member is non-publicC.9: Minimize exposure of membersSubsections:
C.concrete: Concrete typesC.ctor: Constructors, assignments, and destructorsC.con: Containers and other resource handlesC.lambdas: Function objects and lambdasC.hier: Class hierarchies (OOP)C.over: Overloading and overloaded operatorsC.union: Unions類是一種自定義型別,程式設計師可以定義它的表示,操作和介面。 類層次用於把相關的類組織到層次化的結構當中。
類的規則概覽:
C.1: 把相關的資料組織到結構中(struct 或 class)C.2: 當類具有不變式時使用 class;當資料成員可以獨立進行變動時使用 structC.3: 用類來表示介面和實現之間的區別C.4: 僅當函式直接訪問類的內部表示時才讓函式作為其成員C.5: 把輔助函式放在其所支援的類相同的名稱空間之中C.7: 不要在同一個語句中同時定義類或列舉並宣告該型別的變數C.8: 當有任何非公開成員時使用 class 而不是 structC.9: 讓成員的暴露最小化子章節:
C.concrete: 具體型別C.ctor: 建構函式,賦值和解構函式C.con: 容器和其他資源包裝C.lambdas: 函式物件和 lambdaC.hier: 類層次(OOP)C.over: 過載和運算子過載C.union: 聯合體A value of regular type can be copied and the result of a copy is an independent object with the same value as the original.
If a concrete type has both = and ==, a = b should result in a == b being true.
具體型別是一種最簡單的類。 可以對正規型別的值進行復制,複製的結果是一個與原始物件具有相同的值的獨立物件
堆疊的區別
(1) 棧上資料是值語義(預設),堆上資料是透過指標和引用方法物件。預設複製上值複製
c++記憶體模型好,java不好,但是2位元組浪費 heap發展很快,簡單是王道。
可用標準庫。或者智慧指標封裝 讓物件語言表現為值寓意。
這就是為什麼標準版為不設計繼承關係。
堆疊都是存放物件的。物件複製方式不一樣。棧上值複製,前後沒有關係。
堆上,透過指標和引用訪問,前後複製同一個物件。
值語義
C.ctor: 建構函式,賦值,和解構函式這些函式控制物件的生存期:建立,複製,移動,以及銷燬。 定義建構函式是為了確保以及簡化類的初始化過程。
以下被稱為預設操作:
預設建構函式: X()複製建構函式: X(const X&)複製賦值: operator=(const X&)移動建構函式: X(X&&)移動賦值: operator=(X&&)解構函式: ~X()預設情況下,編譯器會為這些操作中被使用的進行定義,但這些預設定義可以被抑制掉。
預設操作是一組互相關聯的操作,它們共同實現了物件的生存期語義。 預設情況下,C++ 按照值型別的方式來對待各個類,但並非所有的型別都與值型別相符。
預設操作的規則集合:
C.20: 只要可能,請避免定義任何的預設操作C.21: 如果定義或者 =delete 了任何複製、移動或解構函式,請定義或者 =delete 它們全部C.22: 使預設操作之間保持一致解構函式的規則:
C.30: 如果一個類需要在物件銷燬時執行明確的操作,請為其定義解構函式C.31: 類所獲取的所有資源,必須都在類的解構函式中進行釋放C.32: 如果類中帶有原始指標(T*)或者引用(T&),請考慮它是否是所有者C.33: 如果類中帶有所有權的指標成員,請定義解構函式C.35: 基類的解構函式應當要麼是 public 和 virtual,要麼是 protected 且非 virtualC.36: 解構函式不能失敗C.37: 使解構函式 noexcept建構函式的規則:
C.40: 如果類具有不變式,請為其定義建構函式C.41: 建構函式應當建立經過完整初始化的物件C.42: 當建構函式無法構造有效物件時,應當丟擲異常C.43: 保證可複製(值型別)類帶有預設建構函式C.44: 儘量讓預設建構函式簡單且不丟擲異常C.45: 不要定義僅對資料成員進行初始化的預設建構函式;應當使用成員初始化式C.46: 預設情況下,把單引數的建構函式宣告為 explicitC.47: 按成員宣告的順序對成員變數進行定義和初始化C.48: 對於常量初始化式來說,優先採用類中的初始化式而不是建構函式中的成員初始化式C.49: 優先進行初始化而不是在建構函式中賦值C.50: 當初始化過程中需要體現“虛擬函式行為”時,請使用工廠函式C.51: 用委派建構函式來表示類中所有建構函式的共同行為C.52: 使用繼承建構函式來把建構函式引入到無須進行其他的明確初始化操作的派生類之中複製和移動的規則:
C.60: 使複製賦值非 virtual,接受 const& 的引數,並返回非 const 的引用C.61: 複製操作應當進行復制C.62: 使複製賦值可以安全進行自賦值C.63: 使移動賦值非 virtual,接受 && 的引數,並返回非 const 的引用C.64: 移動操作應當進行移動,並使原物件處於有效狀態C.65: 使移動賦值可以安全進行自賦值C.66: 使移動操作 noexceptC.67: 多型類應當抑制複製操作其他的預設操作規則:
C.80: 當需要明確使用預設語義時,使用 =defaultC.81: 當需要關閉預設行為(且不需要替代的行為)時,使用 =deleteC.82: 不要在建構函式和解構函式中呼叫虛擬函式C.83: 考慮為值型別提供 noexcept 的 swap 函式C.84: swap 不能失敗C.85: 使 swap 函式 noexceptC.86: 使 == 對運算元的型別對稱,並使之 noexceptC.87: 請當心基類的 ==C.89: 使 hash 函式 noexceptC.90: 依靠建構函式和賦值運算子,不要依靠 memset 和 memcpymap& operator=( const map& other );map& operator=( map&& other ); // Move assignment operator
C.22: Make default operations consistentReason
The default operations are conceptually a matched set. Their semantics are interrelated. Users will be surprised if copy/move construction and copy/move assignment do logically different things. Users will be surprised if constructors and destructors do not provide a consistent view of resource management. Users will be surprised if copy and move don't reflect the way constructors and destructors work.
Example, badclass Silly { // BAD: Inconsistent copy operations class Impl { // ... }; shared_ptr<Impl> p;public: Silly(const Silly& a) : p(make_shared<Impl>()) { *p = *a.p; } // deep copy Silly& operator=(const Silly& a) { p = a.p; } // shallow copy // ...};
C.50: 當初始化過程中需要體現“虛擬函式行為”時,請使用工廠函式理由
當基類物件的狀態必須依賴於物件的派生部分的狀態時,需要使用虛擬函式(或等價手段),並最小化造成誤用和不完全構造的物件的機會視窗。
註解工廠的返回型別預設情況下通常應當為 unique_ptr;如果某些用法需要共享,則呼叫方可以將這個 unique_ptr move 到一個 shared_ptr 中。但是,如果工廠的作者已知其所返回的物件的所有用法都是共享使用的話,就可返回 shared_ptr,並在函式體中使用 make_shared 以節省一次分配。
示例,不好class B {public: B() { /* ... */ f(); // 不好: C.82:不要在建構函式和解構函式中呼叫虛擬函式 /* ... */ } virtual void f() = 0;};
示例class B {protected: class Token {};public: explicit B(Token) { /* ... */ } // 建立不完全初始化的物件 virtual void f() = 0; template<class T> static shared_ptr<T> create() // 建立共享物件的介面 { auto p = make_shared<T>(typename T::Token{}); p->post_initialize(); return p; }protected: virtual void post_initialize() // 構造之後立即呼叫 { /* ... */ f(); /* ... */ } // 好: 虛擬函式分派是安全的};class D : public B { // 某個派生類protected: class Token {};public: explicit D(Token) : B( B::Token{} ) {} void f() override { /* ... */ };protected: template<class T> friend shared_ptr<T> B::create();};shared_ptr<D> p = D::create<D>(); // 建立一個 D 的物件
make_shared 要求公開的建構函式。建構函式透過要求一個受保護的 Token 而無法再被公開呼叫,從而避免不完全構造的物件洩漏出去。 透過提供工廠函式 create(),(在自由儲存上)構造物件變得簡便。
註解根據慣例,工廠方法在自由儲存上進行分配,而不是在執行棧或者某個外圍物件之內進行。
C.copy: 複製和移動值型別一般都應當是可以複製的,而類層次中的介面則不應如此。 資源包裝可以複製也可以不能複製。 我們可以基於邏輯因素,也可以為效能原因而將型別定義為可移動的。