[英]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.