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

virtual2 つ目は、ひとかたまりでスワップ アウトしたい動作のクラスターがある場合です。これはどうしてあなたにインターフェースクラスを叫ばないのですか?

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関数ポインターまたは仮想テーブルのオーバーヘッドなしでメソッドを呼び出します (メソッドのインライン化の許可まで)。

2 番目の利点として、チャネルで実行する操作の山全体が非常に均一になり、各チャネルのオフセットがどうなるかが問題になります。したがって、次のようにするだけで、上記の 2 つの特殊化をなくすことができます。

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ポインターがスワップアウトされます。ChannelSpecificvtable のオーバーヘッドが大きすぎるために使用方法にボトルネックがあることがわかった場合は、リファクタリングしてメガ関数をそこに入れます。

常にを渡しているという事実が嫌な場合は、内部的にへのポインターの状態をCImage与えることができ、コードはにアクセスするために使用できるようになります。ChannelSpecificCImagethis->cimageCImage

一方、上で書いたようなコードにはその場所があります。case switch大規模な声明よりも優れていると思います。

上記のコードの一部は C++11 固有 ( enum classenumストレージ指定子付き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を作成します。( 、または、さまざまなことに応じて --デフォルトで使用します。)unionChannelSpecificImplstd::unique_ptr<ChannelSpecific> channelSpecificstd::shared_ptrunique_ptr

最後に、そのステートメントを維持するのにうんざりしている場合switch(私がそうする傾向があります)、ifテンプレート メタプログラミングを介してカスケード ベースのマジック スイッチを作成することはそれほど難しくありません。ChannelSpecific*それらの1つを呼び出します。(悲しいことに、実際の switch ステートメントを生成する可変個のテンプレート展開はありませんが、コンパイラは、チェーン化された順次 if をとにかく同じ構造に最適化する可能性があります)。

上記のコードから何も得られない場合、重要な部分はゼロ関数のそれぞれを手書きしたくないということです。自分自身を繰り返さず、一度書き、フォーマット間の違いを特徴クラスに分解し、フォーマットとチャネルのテンプレート関数に、作業を行う関数を生成させ、一度記述する必要があります。これを行わないと、マクロを介してコードを生成し、デバッグ不能な混乱が発生するか、他の方法でコードを生成する必要があります (生成されたコードだけで、ジェネレーターをデバッグすることはできません)。特定のチャネルに対して特定の操作を行う場合にのみ発生する、QA が見逃すようなまれなケースのバグがあります。今日じゃないかもしれない、明日じゃないかもしれない、

私は、あなたが提案しているように、この「仮想Cスタイルの関数ポインタスワップアウト」で行われた古いチャネルごとのイメージングライブラリを攻撃している最中であり、私が触れる各関数は上記の手法を使用して書き直されます. コードの量を大幅に削減し、信頼性を高め、時にはパフォーマンスを向上させています。なんで?共通の仮定 (pixelstride はピクセル パッキングに等しい、ソースと宛先の pixelstride は等しい) を確認できたので、そのケースではブランチの少ないバージョンを生成し、コーナー ケースではよりブランチの多いバージョンにフォールバックしてから、適用します。それを一気に無数の異なるピクセル反復コードに変換します。既存のマイクロ最適化に加えて、そのようなマイクロ最適化を使用して N 個の異なるピクセル反復コードを維持すると、コストがかかります。

于 2013-04-15T02:40:57.350 に答える
0

関数ポインターの代わりに virtual を使用することに関する @Yakk のコメントは、お金にかかっています。だけでなく、より良いソリューションも提供されます。

ここでの設計上の留保を考えると、次のことに注意する価値があります。

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

同じシグネチャを持っていても、コンポーネントごとに異なる新しい型名を作成します。私がこの道をたどっていたら、予想される一般的な動作を明確にする単一の型名を持つことになります。

于 2013-04-15T02:47:04.507 に答える