0

这个一般概念会被认为是“坏的”吗?使用函数 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_
4

3 回答 3

5

上面的代码有很多问题。

#define没用,typedef你应该有一个enum

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指针被换出。ChannelSpecific如果我发现由于 vtable 开销过多而导致我的使用方式出现瓶颈,我将进行重构并在其中放入一个宏功能。

如果我讨厌我一直在传递的事实,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::variantunion每个可能的ChannelSpecificImpl. ( std::unique_ptr<ChannelSpecific> channelSpecific, 或std::shared_ptr, 取决于各种情况——unique_ptr默认使用。)

最后,如果您厌倦了维护该switch语句(正如我倾向于维护的那样),if通过模板元编程制作一个基于级联的魔术开关并不难——甚至是一个函数指针工厂数组,它们生成一个ChannelSpecific*和一个显式数组查找打电话给其中一个。(遗憾的是,没有产生实际 switch 语句的可变模板扩展,但编译器可能会将链式顺序 if 优化为相同的结构)。

如果您从上述代码中一无所获,重要的部分是您不想手写每个零函数。您不想重复自己,只需编写一次,将格式之间的差异分解为特征类,并让格式和通道上的模板函数生成完成工作并编写一次的函数。如果您不这样做,您将不得不通过宏生成代码并产生不可调试的混乱,通过其他方法生成代码(并且无法调试生成器,只能调试生成的代码),或者您将只有在对您的 QA 将错过的某些特定频道执行某些特定操作时才会出现一些极端情况错误。也许不是今天,也许不是明天,

我正在攻击一个旧的每通道成像库,该库是在这个“虚拟 C 风格的函数指针交换”中完成的,几乎完全按照你的提议,并且我接触的每个函数都使用上述技术重写。我通过大量减少代码量,提高可靠性,有时甚至提高性能。为什么?因为我能够检查常见的假设——pixelstride 等于像素打包,pixelstride in source 和 dest 相等——并为这种情况生成一个较少分支的版本,并为极端情况回退到更多分支,然后应用一下子就实现了无数不同的像素迭代代码。在现有的微优化之上使用这种微优化来维护 N 个不同的像素迭代代码将是昂贵的:

于 2013-04-15T02:40:57.350 回答
0

Typedef 是使代码更具可读性的好方法。如果您对 typedef 有疑问,则意味着它对代码的可读性没有帮助。只需更改 typedef 名称即可解决问题,但您需要在现有代码库中的任何地方更改它。

于 2013-04-15T02:36:00.043 回答
0

@Yakk 关于使用虚拟而不是函数指针的评论很划算;以及更好的解决方案也提供。

鉴于对此处设计的保留,值得注意的是:

  // typedef the functions
  typedef void(*lpfnDeleteRedComponentProc)();
  typedef void(*lpfnDeleteGreenComponentProc)();
  typedef void(*lpfnDeleteBlueComponentProc)();

为每个组件创建不同的新类型名称,即使它们具有相同的签名。如果我沿着这条路走,我将有一个单一的类型名称,它可以明确预期的常见行为。

于 2013-04-15T02:47:04.507 回答