[英]Is it a poor programming practice to use function typedefs extensivly in classes?
這個一般概念會被認為是“不好的”嗎? 使用函數typedef來預先計算哪個函數可以更好地處理存儲的數據的概念? 還是我應該堅持if和switch語句,以防止其他程序員畏縮? 除了強調此示例中的名稱之外:
#ifndef __PROJECT_CIMAGE_H_
#define __PROJECT_CIMAGE_H_
#define FORMAT_RGB 0
#define FORMAT_BGR 1
typedef unsigned char ImageFormat;
class CImage
{
protected:
// image data
Components* data;
ImageFormat format;
// typedef the functions
typedef void(*lpfnDeleteRedComponentProc)();
typedef void(*lpfnDeleteGreenComponentProc)();
typedef void(*lpfnDeleteBlueComponentProc)();
// specify the different functions for each supported format
void DeleteRedComponentRGB();
void DeleteGreenComponentRGB();
void DeleteBlueComponentRGB();
void DeleteRedComponentBGR();
void DeleteGreenComponentBGR();
void DeleteBlueComponentBGR();
// Add in references to which functions to use.
lpfnDeleteRedComponentProc DRC;
lpfnDeleteGreenComponentProc DGC;
lpfnDeleteBlueComponentProc DBC;
public:
Image(); // Allocate some basic data
~Image(); // Deallocate stored data
// change the image format
void SetImageFormat(ImageFormat format)
{
// shift through the data and adjust it as neccissary.
switch (format)
{
case FORMAT_RGB:
// use functions specially suited for the RGB format
DRC = DeleteRedComponentRGB;
DGC = DeleteGreenComponentRGB;
DBC = DeleteBlueComponentRGB;
break;
case FORMAT_BGR:
// use functions specially suited for the BGR format
DRC = DeleteRedComponentBGR;
DGC = DeleteGreenComponentBGR;
DBC = DeleteBlueComponentBGR;
break;
}
}
// Set's the specifyed component to 0 throughout the entire image
void DeleteRedComponent() { DRC(); }
void DeleteGreenComponent() { DGC(); }
void DeleteBlueComponent() { DBC(); }
// more, similarly pourposed, functions here...
};
#endif // __PROJECT_CIMAGE_H_
上面的代碼有很多問題。
您無用地使用#define
,並在應該有enum
位置使用typedef
enum class ImageFormat:unsigned char { // unsigned char optional
FORMAT_RGB, // =0 optional
FORMAT_BGR // =1 optional
};
其次,您具有一簇想要交換的virtual
行為。 這怎么不給您尖叫接口類?
struct ChannelSpecific {
virtual void DeleteGreen( CImage* ) = 0;
virtual void DeleteBlue( CImage* ) = 0;
virtual void DeleteRed( CImage* ) = 0;
// etc
};
template< ImageFormat format >
struct ChannelSpecificImpl;
template<>
struct ChannelSpecificImpl<FORMAT_RGB>:ChannelSpecific {
void DeleteGreen( CImage* ) final { /* etc...*/ }
// etc...
};
template<>
struct ChannelSpecificImpl<FORMAT_BGR>:ChannelSpecific {
// etc...
};
調用上述virtual
函數的開銷略高於函數指針(由於vtable不太可能位於高速緩存中),但是在連續執行大量操作的情況下,您可以找到格式並顯式轉換工作程序並調用final
方法,而不會產生函數指針或虛擬表的開銷(最多且包括允許內聯這些方法)。
第二個優點是,您要在通道上執行的所有操作最終都變得非常統一,而與每個通道的偏移量有關。 因此,通過執行以下操作,我可以消除上面的兩個專業:
enum class Channel { Red, Green, Blue };
template<ImageFormat, Channel> struct channel_traits;
template<> struct channel_traits<FORMAT_RGB, Red>:std::integral_constant< size_t, 0 > {};
template<> struct channel_traits<FORMAT_RGB, Green>:std::integral_constant< size_t, 1 > {};
template<> struct channel_traits<FORMAT_RGB, Blue>:std::integral_constant< size_t, 2 > {};
template<> struct channel_traits<FORMAT_BGR, Red>:std::integral_constant< size_t, 2 > {};
template<> struct channel_traits<FORMAT_BGR, Green>:std::integral_constant< size_t, 1 > {};
template<> struct channel_traits<FORMAT_BGR, Blue>:std::integral_constant< size_t, 0 > {};
現在,我無需特殊化即可編寫ChannelSpecificImpl<ImageFormat>
-我只需要訪問上述特征類,就可以編寫一次代碼,並多次使用。
在CImage
內部,我存儲了一個ChannelSpecific
指針,該指針不包含任何數據,僅包含算法。 當我換出圖像格式時, ChannelSpecific
指針被換出。 如果由於太多的vtable開銷而發現我在使用ChannelSpecific
方面遇到瓶頸,那么我可以進行重構並在其中添加大型功能。
如果我討厭一直在傳遞CImage
的事實,則可以在內部給CImage
指針指定ChannelSpecific
狀態,現在代碼可以使用this->cimage
來訪問CImage
。
另一方面,像您上面編寫的代碼一樣,它占有一席之地。 我認為它比大量的case
switch
語句更好。
請注意,上面的代碼中有一些是C ++ 11特有的( enum class
,帶有存儲說明符的enum
, final
),但是如果刪除這些功能,該解決方案仍然可行。
還要注意,您的switch
語句最終看起來像:
switch (format) {
case FORMAT_RGB:
channelSpecific.reset(new ChannelSpecificImpl<FORMAT_RGB>());
case FORMAT_BGR:
channelSpecific.reset(new ChannelSpecificImpl<FORMAT_BGR>());
它幾乎不需要維護,也不太可能包含錯誤。 如果您討厭免費商店(更具體地說,已經發現格式更改非常普遍,以致::new
調用對性能至關重要,那么請創建一個boost::variant
或每個C ++ 11 union
可能的ChannelSpecificImpl
。 ( std::unique_ptr<ChannelSpecific> channelSpecific
或std::shared_ptr
,取決於各種情況-默認情況下使用unique_ptr
。)
最后,如果您厭倦了維護該switch
語句(按照我的意願), if
通過模板元編程進行基於級的魔術開關的級聯並不難-甚至是產生ChannelSpecific*
和顯式數組查找以調用其中之一。 (遺憾的是,沒有可變模板擴展生成實際的switch語句,但是編譯器可能會將鏈式順序ifs優化為相同的結構)。
如果以上代碼沒有任何幫助,則重要的部分是您不想手動編寫每個零函數。 您不希望重復自己,只寫一次,將孔之間的差異分解為特征類,並在格式和通道上使模板函數產生起作用的函數並編寫一次。 如果您不這樣做,則要么必須通過宏來生成代碼,否則將陷入混亂,不能通過其他方法來生成代碼(並且不能調試生成器,只能調試生成的代碼),或者您將僅在對您的質量檢查人員會錯過的某些特定渠道進行某些特定操作時才會出現一些極端情況的錯誤。 也許不是今天,也許不是明天,但是有一天進行了更改,有人將更新升級到第18種格式特定功能,但僅限於藍色通道。
我正在攻擊一個舊的每通道映像庫,該庫完全按照您的建議通過這種“虛擬C樣式函數指針交換”完成,並且我觸摸的每個函數都使用上述技術重寫。 我正在大量減少代碼量,提高可靠性,有時甚至提高性能。 為什么? 因為我能夠檢查常見的假設-pixelsride等於pixel pack,pixelsride等於source和dest等於-並為該情況生成了一個較不易破解的版本,而對於拐角情況又退回至一個較不易破解的版本,然后應用一口氣就完成了無數不同的像素迭代代碼。 在現有的微優化之上使用這種微優化來維護N個不同的像素迭代代碼將是昂貴的:以這種方式執行意味着我要編寫一次,並獲得N倍的收益。
Typedef是使代碼更具可讀性的好方法。 如果您對typedef有疑問,則意味着它只是不有助於代碼的可讀性。 只需更改typedef名稱即可解決該問題,但是您需要在現有代碼庫的任何位置進行更改。
@Yakk關於使用虛擬指針而不是函數指針的評論很有價值; 以及更好的解決方案。
考慮到此處對設計的保留,值得注意的是:
// typedef the functions
typedef void(*lpfnDeleteRedComponentProc)();
typedef void(*lpfnDeleteGreenComponentProc)();
typedef void(*lpfnDeleteBlueComponentProc)();
為每個組件創建不同的新類型名稱,即使它們具有相同的簽名。 如果我沿着這條路走,我將有一個單一的類型名稱,這將使預期的常見行為清晰明了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.