簡體   English   中英

何時將一個小類分成頭文件和cpp文件?

[英]When to separate a small class into header and cpp files?

我知道之前已經回答了類似的問題但是我已經搜索了stackoverflow(等)並且沒有找到關於如何處理實例化並在程序中僅使用一次的小類的明確想法。 在單獨的文件中保留聲明和實現是否真的很重要?

請看以下示例:

// timer.hpp

#pragma once
#include "presets.h" // includes #defines for u64 -> uint64_t, etc

class Timer {
    public:
        Timer() {}
        Timer(u64 elt) : elt_(elt) {}

        void startTiming() { if (NOT running_){ running_ = true;  sTime_ = GetTickCount64(); }};
        void stopTiming() { if (running_)     { running_ = false; eTime_ = GetTickCount64(); elt_ += (eTime_ - sTime_); sTime_ = eTime_; }}

        u64 getElapsed() { if (NOT running_) return elt_; eTime_ = GetTickCount64(); return elt_ + eTime_ - sTime_; }
    private:
        bool running_ = true;
        u64 elt_ = 0, eTime_ = 0, sTime_ = GetTickCount64();
};

我讀過的所有內容都堅持聲明和實現是在單獨的文件中,但將這樣一個簡單的類拆分為.h文件和.cpp文件似乎很荒謬。 它很少被改變並且只被實例化一次。

我有一些其他類,更大一點,也只使用我目前在2個文件中的一次。 假設一個類只在程序中實例化一次,我的問題是:

  1. 將聲明和實現小類放在單個文件中是否合理? 如果沒有,為什么不呢?
  2. 一個類需要多大才能最好分成2個文件?

我知道這里已經存在非常類似的問題,但我沒有看到任何對上述內容給出明確答案的問題。

  1. 將聲明和實現小類放在單個文件中是否合理?

是的,將一個小類的聲明和實現放在一個文件中是合理的。

如果沒有,為什么不呢?

因為如果修改了函數定義,則需要重新編譯依賴於類定義的所有轉換單元 - 或者更確切地說,需要重新編譯包含該定義的所有轉換單元。 這包括所有依賴於類的,因為它們必須包含定義,但也包括無償定義的定義。

  1. 一個類需要多大才能最好分成2個文件?

沒有硬性限制。 它取決於許多變量,並且受個人偏好的影響很大。

有些人將每個類的所有成員函數定義放在一個翻譯單元中,因為這是他們知道事情是如何完成的,或者因為他們有必須遵循的編碼標准。

其他人發誓,如果沒有通過在標題中內聯定義所有函數所允許的優化可能性,它們就無法生存,因此整個程序只有一個翻譯單元。 這還具有從頭開始減少編譯時間的效果,但也會導致整個項目在任何更改時重建。

但是沒有必要教條地遵循這些路徑中的任何一條,並且兩者都不一定是最優的 - 兩者都有缺點和優點。 因此,一個好的選擇可能介於這些路徑之間。 但正如我上面所說,選擇取決於許多變量,因此使用快速啟發式而非完整分析可能更好。 以下是一些,如果您找到合適的推理,您可以遵循:

  • 如果函數定義為空,那么它是內聯定義的一個非常好的候選者。
  • 如果函數或類是模板,則必須內聯定義 - 您沒有選項(除非您可以限制可能的模板實例化的數量)。
  • 如果函數的定義依賴於定義,則該類的定義不依賴於該定義。 如果您定義內聯函數,則會導致依賴項傳播。 在這種情況下,不定義內聯函數是一個非常好的主意。
  • 如果您習慣於修改函數的定義而非極少,並且定義在許多翻譯單元中使用,那么不定義內聯函數可能是個好主意,否則您可能會發現自己重新編譯大多數項目一次又一次。 如果項目很小並且因此可以快速編譯,那么這種啟發式方法無關緊要。 此外,使用類的地方數量通常很難跟蹤,因此假設最差的情況通常更簡單。
  • 如果您對程序進行概要分析並找到每秒鍾調用一百萬次的特定函數,那么它就是內聯定義的一個很好的候選者,可以利用內聯擴展優化。 請注意,即使在單獨的翻譯單元中定義了函數,使用鏈接時優化也可以允許內聯擴展。
  • 如果函數編譯速度慢,那么最好不要內聯定義它。 現在,知道哪些函數編譯起來很慢並不容易理解,所以你需要一套啟發式算法。 以下是一些,如果您找到合適的推理,您可以遵循:
    • 較長的函數定義通常比較短的函數定義慢。
    • 實例化模板的函數有時編譯起來很慢。

假設一個類只在程序中實例化一次

實例化的數量與選擇無關 - 除了內聯構造函數在這種情況下對性能可能不重要。


PS

雖然它非常便攜,但#pragma once是非標准的。 我不是說你應該擺脫它; 這只是需要注意的事情。

GetTickCount64是特定於系統且不可移植的。 C ++在<chrono>標頭中有一個標准時鍾API。

  1. 我認為這取決於。 如果您在團隊中工作,請遵循團隊接受的標准。 如果編碼標准說你應該將聲明和實現分成單獨的文件(.h和.cpp),這是唯一的編碼方式,你應該這樣做。

否則,您在.h文件中創建類的方式也沒關系,我經常在單獨的頭文件中創建枚舉。 如果一個人可以聲明一個類並提供內聯成員函數,那么,為了在同一時間定義類,為什么不這樣做呢? 它減少了文件數量,代碼量,使其更簡潔。

重要說明:我將提供一個注釋,解釋我在頭文件中聲明和定義類的原因。 其他開發人員將能夠理解我的動機。

  1. 沒有一定的界限。 需要多少塊石頭才能制成最小尺寸的一堆? 你能分辨出一個夜晚變成沒有鍾表的日子嗎? 這只是一個直覺問題。 如果你有一個非常簡單的類,你可以從頭文件開始。 如果您的類變得越來越大,您可以重構它並添加源文件並在那里移動定義。

這是另一個考慮因素: 編譯性能

請注意,您的一體化聲明+定義文件使用GetTickCount64 這個文件需要包含Windows.h - 一個相當龐大的野獸(盡管間接在你的情況下,通過你的presets.h,毫無疑問),因為GetTickCount64在Windows.h中聲明。 (如果您的計時器返回類型(u64)不依賴於特定於Windows平台的類型,則這尤其重要。

如果將聲明與定義分開,那么只有定義(.cpp文件)需要包含Windows.h。 聲明(.h)不需要它。

這意味着包含您的Timer.h文件的任何.cpp文件都不會被強制包含Windows.h。 這樣可以減少Timer.h的所有使用者的編譯時間和內存需求,最終可以加快編譯速度。 可能有許多.h文件的消費者(項目中需要包含你的Timer.h文件的許多其他.cpp文件)但是只需要編譯一個.cpp來生成Timer類的定義(你的Timer .cpp),這是唯一一個實際需要包含該函數的Windows.h頭文件的人。

隨着應用程序規模的擴大,您希望創建越來越多獨立於平台的模塊,這些模塊依賴於簡單的抽象,例如Timer類,而不是原始的Win32 / Win64 API調用,如GetTickCount64。 大部分應用程序可以只是應用程序的業務邏輯,而不依賴於包含針對大型API(如Win32 API)的重量級平台特定標頭。

第2章

這項工作的下一部分是將類的私有部分與接口的公共部分分開,以進一步隔離代碼並使頭部保持較小。 在您的特定示例中,您的類聲明中沒有任何特殊內容可以隔離,但在許多情況下,該類可能需要一個私有成員變量,其類型與公共接口不關心。 沒有必要讓你的Timer.h頭文件的使用者負擔到你可能需要放入私有變量的所有類型事物的定義; 僅限屬於您的公共API的類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM