在类中使用函数typedef是一种糟糕的编程习惯吗?

Wol*_*ler 0 c++ typedef function

这个一般概念会被视为"坏"吗?使用函数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_
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 5

上述代码存在许多问题.

你无#define用地使用,typedef你应该在哪里使用enum

enum class ImageFormat:unsigned char { // unsigned char optional
  FORMAT_RGB, // =0 optional
  FORMAT_BGR // =1 optional
};
Run Code Online (Sandbox Code Playgroud)

其次,您有一组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...
};
Run Code Online (Sandbox Code Playgroud)

调用上述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 > {};
Run Code Online (Sandbox Code Playgroud)

现在我开始编写我的ChannelSpecificImpl<ImageFormat>专业 - 我只需要访问上面的traits类,我就可以编写一次代码,并多次使用它.

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>());
Run Code Online (Sandbox Code Playgroud)

这远远不能维护,也不太可能包含bug.如果你讨厌免费商店(更具体地说,已经发现格式变化很常见,以至于::new调用是重要的性能影响),那么创建一个boost::variantunion每个可能的C++ 11 ChannelSpecificImpl.(std::unique_ptr<ChannelSpecific> channelSpecific或者std::shared_ptr,根据不同的东西 - unique_ptr默认使用.)

最后,如果你厌倦了维护那个switch语句(我倾向于),if通过模板元编程实现基于级联的魔术开关并不那么难 - 甚至是一个函数指针工厂的数组,它们产生一个ChannelSpecific*显式的数组查找到打电话给其中一个.(遗憾的是,没有变量模板扩展产生实际的switch语句,但编译器可能无论如何都将链式顺序ifs优化到相同的结构中).

如果你从上面的代码中得不到任何结果,那么重要的是你不想手写每个零函数.你不想重复自己,写一次,将因素之间的差异分解为traits类,并在格式和通道上设置模板函数,产生执行工作的函数并写入一次.如果你不这样做,你要么必须通过宏生成你的代码并且有一个不可摧毁的混乱,通过其他方法生成代码(并且无法调试生成器,只是生成的代码),或者你将只有在对QA会遗漏的某个特定频道进行某些特定操作时才会出现一些角落案例错误.也许不是今天,也许不是明天,

我正在攻击一个旧的每通道成像库,这个库在这个"虚拟C风格的函数指针交换"中完成,就像你提出的那样,我触摸的每个函数都会被上面的技术重写.我通过大幅度减少代码量,提高可靠性,有时甚至可以提高性能.为什么?因为我能够检查常见的假设 - 像素值等于像素打包,像素值在源和目标相等 - 并为该情况生成一个较少分支的版本,并在角落情况下回退到更多分支,然后应用一举完成了无数不同的像素迭代代码.在现有的微优化之上使用那种微优化来维护N个不同的像素迭代代码将是昂贵的: