简体   繁体   English

在类中广泛使用函数typedef是不好的编程习惯吗?

[英]Is it a poor programming practice to use function typedefs extensivly in classes?

Would this general concept be considered "bad"? 这个一般概念会被认为是“不好的”吗? The concept of using function typedefs for precalculating which function's are better optimized to handle the data stored? 使用函数typedef来预先计算哪个函数可以更好地处理存储的数据的概念? Or should I just stick to if and switch statements to keep other programmers from cringing? 还是我应该坚持if和switch语句,以防止其他程序员畏缩? Aside from cringing at the names in this throw together example: 除了强调此示例中的名称之外:

#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_

There are many problems with the above code. 上面的代码有很多问题。

You use #define uselessly, and typedef where you should have an enum 您无用地使用#define ,并在应该有enum位置使用typedef

enum class ImageFormat:unsigned char { // unsigned char optional
  FORMAT_RGB, // =0 optional
  FORMAT_BGR // =1 optional
};

Second, you have a cluster of virtual behavior you want to swap out in a clump. 其次,您具有一簇想要交换的virtual行为。 How does this not scream interface class to you? 这怎么不给您尖叫接口类?

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...
};

The overhead to calling the above virtual functions is marginally higher than a function pointer (due to the vtable being less likely to be in the cache), but in cases where you are doing a whole pile of operations in a row you can find the format and explicitly cast the worker and call the final methods with no function-pointer or virtual table overhead (up to and including allowing the methods to be inlined). 调用上述virtual函数的开销略高于函数指针(由于vtable不太可能位于高速缓存中),但是在连续执行大量操作的情况下,您可以找到格式并显式转换工作程序并调用final方法,而不会产生函数指针或虚拟表的开销(最多且包括允许内联这些方法)。

As a second advantage, a whole pile of the operations you want to perform on channels ends up being exceedingly uniform, and just a matter of what the offset of each channel is. 第二个优点是,您要在通道上执行的所有操作最终都变得非常统一,而与每个通道的偏移量有关。 So I can do away with the two above specializations by simply doing this: 因此,通过执行以下操作,我可以消除上面的两个专业:

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 > {};

and now I get to write my ChannelSpecificImpl<ImageFormat> without specializations -- I just need to access the above traits classes, and I get to write my code once, and use it multiple times. 现在,我无需特殊化即可编写ChannelSpecificImpl<ImageFormat> -我只需要访问上述特征类,就可以编写一次代码,并多次使用。

Inside CImage I store a single ChannelSpecific pointer, which holds no data, just algorithms. CImage内部,我存储了一个ChannelSpecific指针,该指针不包含任何数据,仅包含算法。 When I swap out the image format, the ChannelSpecific pointer is swapped out. 当我换出图像格式时, ChannelSpecific指针被换出。 If I find I have a bottleneck in how I'm using ChannelSpecific because of too much vtable overhead, I get to refactor and put a mega-function in it. 如果由于太多的vtable开销而发现我在使用ChannelSpecific方面遇到瓶颈,那么我可以进行重构并在其中添加大型功能。

If I hate the fact that I'm passing in the CImage all the time, I can give ChannelSpecific state of a pointer to the CImage internally, and now the code gets to use this->cimage to access the CImage . 如果我讨厌一直在传递CImage的事实,则可以在内部给CImage指针指定ChannelSpecific状态,现在代码可以使用this->cimage来访问CImage

On the other hand, code like what you wrote above has its place. 另一方面,像您上面编写的代码一样,它占有一席之地。 I'd consider it better than massive case switch statements. 我认为它比大量的case switch语句更好。

Note that a bit of the above code is C++11 specific ( enum class , enum with storage specifier, final ), but if you drop those features the solution is still viable. 请注意,上面的代码中有一些是C ++ 11特有的( enum class ,带有存储说明符的enumfinal ),但是如果删除这些功能,该解决方案仍然可行。

Also note that your switch statement ends up looking like: 还要注意,您的switch语句最终看起来像:

switch (format) {
  case FORMAT_RGB:
    channelSpecific.reset(new ChannelSpecificImpl<FORMAT_RGB>());
  case FORMAT_BGR:
    channelSpecific.reset(new ChannelSpecificImpl<FORMAT_BGR>());

which is far less to maintain and less likely to contain bugs. 它几乎不需要维护,也不太可能包含错误。 If you hate the free store (and more specifically, have found that format changes are common enough that the ::new call is a performance hit that matters), create a boost::variant or a C++11 union of each of the possible ChannelSpecificImpl . 如果您讨厌免费商店(更具体地说,已经发现格式更改非常普遍,以致::new调用对性能至关重要,那么请创建一个boost::variant或每个C ++ 11 union可能的ChannelSpecificImpl ( std::unique_ptr<ChannelSpecific> channelSpecific , or std::shared_ptr , depending on various things -- use unique_ptr by default.) std::unique_ptr<ChannelSpecific> channelSpecificstd::shared_ptr ,取决于各种情况-默认情况下使用unique_ptr 。)

Finally, if you get tired of maintaining that switch statement (as I tend to), making a cascading if based magic switch via template metaprogramming isn't that hard -- or even an array of function pointer factories that produce a ChannelSpecific* and an explicit array lookup to call one of them. 最后,如果您厌倦了维护该switch语句(按照我的意愿), if通过模板元编程进行基于级的魔术开关的级联并不难-甚至是产生ChannelSpecific*和显式数组查找以调用其中之一。 (sadly, there isn't a variardic template expansion that produces an actual switch statement, but compilers might optimize chained sequential ifs into the same structure anyhow). (遗憾的是,没有可变模板扩展生成实际的switch语句,但是编译器可能会将链式顺序ifs优化为相同的结构)。

If you get nothing from the above code, the important part is that you don't want to hand write each of the zero functions. 如果以上代码没有任何帮助,则重要的部分是您不想手动编写每个零函数。 You want to not repeat yourself, write it once, factor out the differences between the foramts into a traits class, and have template functions on the format and channel produce the function that does the work and be written once. 您不希望重复自己,只写一次,将孔之间的差异分解为特征类,并在格式和通道上使模板函数产生起作用的函数并编写一次。 If you don't do this, you will either have to generate your code via macros and have an undebuggable mess, generate your code via some other method (and not be able to debug the generator, just the generated code), or you will have some corner case bug that occur only when doing some specific operation to some specific channel that your QA will miss. 如果您不这样做,则要么必须通过宏来生成代码,否则将陷入混乱,不能通过其他方法来生成代码(并且不能调试生成器,只能调试生成的代码),或者您将仅在对您的质量检查人员会错过的某些特定渠道进行某些特定操作时才会出现一些极端情况的错误。 Maybe not today, maybe not tomorrow, but someday when a change is made and someone screws up the update to the 18th format specific function but only in the blue channel. 也许不是今天,也许不是明天,但是有一天进行了更改,有人将更新升级到第18种格式特定功能,但仅限于蓝色通道。

I'm in the midst of attacking an old per-channel imaging library that was done in this "virtual C-style function pointer swap out" pretty much exactly as you are proposing, and each function I touch gets rewritten using the above technique. 我正在攻击一个旧的每通道映像库,该库完全按照您的建议通过这种“虚拟C样式函数指针交换”完成,并且我触摸的每个函数都使用上述技术重写。 I am reducing the amount of code by huge swaths, increasing reliability, and sometimes even getting performance boosts. 我正在大量减少代码量,提高可靠性,有时甚至提高性能。 Why? 为什么? Because I was able to check common assumptions -- pixelstride equal to pixel packing, pixelstride in source and dest equal -- and generate a less-branchy version for that case, and fall back to a more-branchy for the corner cases, then apply that to a whole myriad of different pixel iteration code in one fell swoop. 因为我能够检查常见的假设-pixelsride等于pixel pack,pixelsride等于source和dest等于-并为该情况生成了一个较不易破解的版本,而对于拐角情况又退回至一个较不易破解的版本,然后应用一口气就完成了无数不同的像素迭代代码。 Maintaining N different pixel iterating code with that kind of micro optimization on top of the existing micro optimizations would be expensive: doing it this way means that I get to write it once, and reap the benefits N fold. 在现有的微优化之上使用这种微优化来维护N个不同的像素迭代代码将是昂贵的:以这种方式执行意味着我要编写一次,并获得N倍的收益。

Typedefs are a good way to make the code much more readable. Typedef是使代码更具可读性的好方法。 If you have a problem with the typedefs then it means it just does not contribute to the code's readability. 如果您对typedef有疑问,则意味着它只是不有助于代码的可读性。 Just changing the typedef name will solve the problem but you would need to change it everywhere in the existing codebase. 只需更改typedef名称即可解决该问题,但是您需要在现有代码库的任何位置进行更改。

@Yakk comments on using virtual instead of function pointers are on the money; @Yakk关于使用虚拟指针而不是函数指针的评论很有价值; as well as the better solution also offered. 以及更好的解决方案。

Given the reservations on the design here, its worth noting that: 考虑到此处对设计的保留,值得注意的是:

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

creates distinct new type names for each component, even though they have the same signature. 为每个组件创建不同的新类型名称,即使它们具有相同的签名。 If I were going down this path I'd have a single type name which would make clear the expected common behavior. 如果我沿着这条路走,我将有一个单一的类型名称,这将使预期的常见行为清晰明了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM