是否会考虑这个一般概念"糟糕"?使用函数typedef预先计算哪个函数的概念更好地优化以处理存储的数据?或者我应该坚持if和切换语句以防止其他程序员畏缩?除了把这个名字放在一起的例子:
#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_
答案 0 :(得分:5)
上述代码存在许多问题。
您无用地使用#define
,typedef
应该使用enum
enum class ImageFormat:unsigned char { // unsigned char optional
FORMAT_RGB, // =0 optional
FORMAT_BGR // =1 optional
};
其次,您有一群virtual
行为要在clump中换出。这对你来说怎么不尖叫接口类?
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不太可能在缓存中),但是在你正在进行大量操作的情况下你可以在一行中找到格式并显式地转换worker并调用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>
而没有专业化 - 我只需要访问上面的traits类,我就可以编写一次代码,并多次使用它。
在CImage
内部我存储了一个ChannelSpecific
指针,它不包含任何数据,只包含算法。当我换出图像格式时,ChannelSpecific
指针被换出。如果我发现由于vtable开销太大而导致我使用ChannelSpecific
的方式存在瓶颈,我会重构并在其中添加一个超级函数。
如果我讨厌我一直在CImage
传递的事实,我可以在内部给ChannelSpecific
指向CImage
的状态,现在代码获得使用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 {{ 1}}每个可能的union
。 (ChannelSpecificImpl
或std::unique_ptr<ChannelSpecific> channelSpecific
,视各种情况而定 - 默认使用std::shared_ptr
。)
最后,如果你厌倦了维护unique_ptr
语句(我倾向于),那么通过模板元编程实现基于级联switch
的魔术开关并不那么难 - 甚至是数组函数指针工厂生成if
和显式数组查找以调用其中一个。 (遗憾的是,没有变量模板扩展产生实际的switch语句,但编译器可能无论如何都会将链式ifs优化到同一个结构中。)
如果你从上面的代码中得不到任何结果,那么重要的是你不想手写每个零函数。你不想重复自己,写一次,将因素之间的差异分解为traits类,并在格式和通道上设置模板函数,产生执行工作的函数并写入一次。如果你不这样做,你要么必须通过宏生成你的代码并且有一个不可摧毁的混乱,通过其他方法生成你的代码(并且不能调试生成器,只是生成的代码),或者你将只有在对QA会遗漏的某个特定频道进行某些特定操作时才会出现一些角落案例错误。也许不是今天,也许不是明天,但有一天当做出改变并且有人将更新搞砸到第18格式特定功能但仅在蓝色通道中。
我正在攻击一个旧的每通道成像库,这个库在这个“虚拟C风格函数指针交换”中完成,就像你提出的那样,我触摸的每个函数都会被重写以上技术。我通过大幅度减少代码量,提高可靠性,有时甚至可以提高性能。为什么?因为我能够检查常见的假设 - 像素值等于像素打包,像素值在源和目标相等 - 并为该情况生成一个较少分支的版本,并在角落情况下回退到更多分支,然后应用一举完成了无数不同的像素迭代代码。在现有的微优化之上使用这种微优化来维护N个不同的像素迭代代码将是昂贵的:这样做意味着我可以编写一次,并获得N折叠的好处。
答案 1 :(得分:0)
Typedef是使代码更具可读性的好方法。如果你对typedef有问题,那么这意味着它只是没有贡献代码的可读性。只需更改typedef名称即可解决问题,但您需要在现有代码库中的任何位置进行更改。
答案 2 :(得分:0)
@Yakk关于使用虚拟而不是函数指针的评论是关于钱的;以及提供的更好的解决方案。
鉴于此处对设计有所保留,值得注意的是:
// typedef the functions
typedef void(*lpfnDeleteRedComponentProc)();
typedef void(*lpfnDeleteGreenComponentProc)();
typedef void(*lpfnDeleteBlueComponentProc)();
为每个组件创建不同的新类型名称,即使它们具有相同的签名。如果我沿着这条路走下去,我会有一个类型名称,可以明确预期的常见行为。