C++開発におけるデータ構造とアルゴリズムの分離についての簡単な説明

C++開発におけるデータ構造とアルゴリズムの分離についての簡単な説明

Windows でプログラムを書いたことがある人なら、誰でも多かれ少なかれビットマップを使ったことがあると思います。ほとんどの人は、成熟した完全な DIB クラス ライブラリ (CxImage、CDIB など) をインターネットからダウンロードして使用していますが、将来簡単に拡張して使用できるように、独自のカプセル化された DIB クラス ライブラリ セットを持っている人もいます。 (GDI+ は近年強力な勢力として登場しました。スケーリング、回転、グラデーションの塗りつぶしなど、特定の処理面で比類のない速度と品質を提供します。ただし、完全な画像処理プログラムを作成する場合、これを直接使用するとアーキテクチャ設計に困難が生じます。使用する前に、アダプター モードでカプセル化することができます)。

このとき、画像処理操作が必要になった場合はどうすればよいでしょうか? OO 経験のない多くのC++プログラマー (1 年前の私のように) は、クラスにメソッドを直接追加するかもしれません。

  1. int FClamp0255 ( int nValue) { return max (0, min (0xFF, nValue));} // 0~255に飽和 
  2. クラスFCObjImage
  3. {
  4.  公共
  5. 反転 () ;
  6. AdjustRGB ( int R, int G, int B);
  7. } ;
  8. void FCObjImage::Invert() 関数
  9. {
  10.  ((GetHandle() == NULL) || (ColorBits() < 24))の場合
  11. 戻る;
  12.  int nSpan = ColorBits() / 8 ; // ピクセルあたりのバイト数 3, 4  
  13.  ( int y=0 ; y < Height() ; y++)の場合
  14. {
  15. BYTE * pPixel = GetBits(y);
  16. ( int x = 0 ; x < 幅() ; x++, pPixel += nSpan)の場合
  17. {
  18. pPixel[0] = ~pPixel[0];
  19. pPixel[1] = ~pPixel[1];
  20. pPixel[2] = ~pPixel[2];
  21. }
  22. }
  23. }
  24. void FCObjImage::AdjustRGB ( int R, int G, int B)
  25. {
  26.  ((GetHandle() == NULL) || (ColorBits() < 24))の場合
  27. 戻る;
  28.  int nSpan = ColorBits() / 8 ; // ピクセルあたりのバイト数 3, 4  
  29.  ( int y=0 ; y < Height() ; y++)の場合
  30. {
  31. BYTE * pPixel = GetBits(y);
  32. ( int x = 0 ; x < 幅() ; x++, pPixel += nSpan)の場合
  33. {
  34. pPixel[0] = FClamp0255 (pPixel[0] + B);
  35. pPixel[1] = FClamp0255 (pPixel[1] + G);
  36. pPixel[2] = FClamp0255 (pPixel[2] + R);
  37. }
  38. }
  39. }

ここでは 2 つの例を示します (それぞれ、色の反転と RGB 値の調整を実現する)。実際には、明るさ、コントラスト、彩度など、このような操作は多数あります。さて、振り返ってみてください。これらのメソッドを追加するには、どのような手順を踏むのでしょうか。おおおお、RCP (同僚の発明、フルネーム: ラピッド コピー ペースト^-^)、最初の手順は、上記のコードの一部をコピーし、インターフェイスと処理部分を変更することです。ここでのデモ コードは短く、バグと一緒にコピーされることはありませんが、時限爆弾がもう 1 つあります。ある日、上司から「長い間待つのは耐えられないので、進捗バーを追加してください…」と言われました。グローバル変数を追加したり、各関数にパラメータを追加したりすることもできますが、同じことが続きます。つまり、これらすべての処理関数のコードを変更する必要があり、内部の呪いによって、それらのいずれかを変更する必要がなくなるわけではありません。この時点で、バグはすでに攻撃の機会をうかがっています... しかし、苦難の時代はまだまだ終わりません。 1 か月後、上司から突然、地域処理機能の追加を依頼され、さらに 1 か月後...

戻ってコードをもう一度見てみますか?そうです、赤いコードを除いて、他のすべてはまったく同じなので、これらのアルゴリズムを分離することは可能ですか?標準ライブラリの qsort や windows でよく使用されるコールバック メソッドをすぐに思い浮かべるかもしれません。では、これを実装してみましょう:

  1. voidピクセル反転 ( BYTE * pPixel)
  2. {
  3. pPixel[0] = ~pPixel[0];
  4. pPixel[1] = ~pPixel[1];
  5. pPixel[2] = ~pPixel[2];
  6. }
  7. void FCObjImage::PixelProcess ( void (__cdecl*PixelProc)( BYTE * pPixel))
  8. {
  9.  ((GetHandle() == NULL) || (ColorBits() < 24))の場合
  10. 戻る;
  11.  int nSpan = ColorBits() / 8 ; // ピクセルあたりのバイト数 3, 4  
  12.  ( int y=0 ; y < Height() ; y++)の場合
  13. {
  14. BYTE * pPixel = GetBits(y);
  15. ( int x = 0 ; x < 幅() ; x++, pPixel += nSpan)の場合
  16. {
  17. ピクセルプロセス (pPixel) ;
  18. }
  19. }
  20. }
  21. void FCObjImage::Invert() 関数
  22. {
  23. ピクセル処理 (Pixel_Invert) ;
  24. }

まあ、それは良いことです。アルゴリズムが 1 つの関数にまで削減され、問題は解決したようです。 Invert の処理はうまくいっていますが、AdjustRGB の処理で問題が発生しました。RGB の 3 つの調整パラメータを渡すにはどうすればよいでしょうか?グローバル変数/メンバー変数を追加することで、インターフェイスにパラメーターが 1 つだけになりますか?これは一つの方法ですが、クラスメソッドの数が増えるとプログラムの可読性や保守性が急激に低下し、変更前よりも影響が悪くなります。

では、高度な抽象化と優れたインターフェースを実現するにはどうすればよいでしょうか?私たちは OO (オブジェクト指向) を現場に招き、その実装について話をしてもらいました。次の派生関係を設計します。

  1. クラスFCSinglePixelProcessBase
  2. {
  3.  公共
  4. バーチャル  void ProcessPixel ( int x, int y, BYTE * pPixel) PURE ;
  5. } ;
  6. クラスFCPixelInvert:パブリックFCSinglePixelProcessBase
  7. {
  8.  公共
  9. バーチャル  void ProcessPixel ( int x, int y, BYTE * pPixel) ;
  10. } ;
  11. void FCPixelInvert::ProcessPixel ( int x, int y, BYTE * pPixel)
  12. {
  13. pPixel[0] = ~pPixel[0]; pPixel[1] = ~pPixel[1]; pPixel[2] = ~pPixel[2];
  14. }
  15. クラスFCPixelAdjustRGB :パブリックFCSinglePixelProcessBase
  16. {
  17.  公共
  18. FCPixelAdjustRGB ( int DeltaR、 int DeltaG、 int DeltaB) ;
  19. バーチャル  void ProcessPixel ( int x, int y, BYTE * pPixel) ;
  20.  保護されています:
  21. 整数m_iDeltaR、m_iDeltaG、m_iDeltaB;
  22. } ;
  23. void FCPixelAdjustRGB::ProcessPixel ( int x, int y, BYTE * pPixel)
  24. {
  25. pPixel[0] = FClamp0255 (pPixel[0] + m_iDeltaB);
  26. pPixel[1] = FClamp0255 (pPixel[1] + m_iDeltaG);
  27. pPixel[2] = FClamp0255 (pPixel[2] + m_iDeltaR);
  28. }

次に、イメージクラスを次のように変更します。

  1. #include "PixelProcessor.h"  
  2. クラスFCObjImage
  3. {
  4.  公共
  5. void PixelHandler (FCSinglePixelProcessBase & PixelProcessor、FCObjProgress * 進行状況 = NULL);
  6. } ;
  7. void FCObjImage::PixelHandler (FCSinglePixelProcessBase & PixelProcessor、FCObjProgress * 進行状況)
  8. {
  9.  (GetHandle() == NULL)の場合
  10. 戻る;
  11.  int nSpan = ColorBits() / 8 ; // ピクセルあたりのバイト数 3, 4  
  12.  ( int y=0 ; y < Height() ; y++)の場合
  13. {
  14. BYTE * pPixel = GetBits(y);
  15. ( int x = 0 ; x < 幅() ; x++, pPixel += nSpan)の場合
  16. {
  17. PixelProcessor.ProcessPixel(x, y, pPixel);
  18. }
  19. (進捗状況がNULLではない場合
  20. progress->SetProgress(y * 100 / Height());
  21. }
  22. }
  23. void FCObjImage::Invert (FCObjProgress * 進行状況)
  24. {
  25. PixelHandler (FCPixelInvert()、進行状況);
  26. }
  27. void FCObjImage::AdjustRGB ( int R, int G, int B, FCObjProgress * 進行状況)
  28. {
  29. PixelHandler (FCPixelAdjustRGB (R,G,B)、進行状況) ;
  30. }

(上記は単なる基本的なフレームワークであり、構築時に RECT パラメータを渡すことで、領域処理パラメータを簡単に追加できます。)

オブジェクトは本当に素晴らしいものです。オブジェクトは、外の世界へのシンプルなインターフェースを提供することができ、また、オブジェクト自体に多くの追加情報をカプセル化することができます。

さて、結果をテストしましょう。画像の奇数行を黒に、偶数行を白に設定する操作を追加します。

  1. クラスFCPixelTest:パブリックFCSinglePixelProcessBase
  2. {
  3.  公共
  4. バーチャル  void ProcessPixel ( int x, int y, BYTE * pPixel) ;
  5. } ;
  6. void FCPixelTest::ProcessPixel ( int x, int y, BYTE * pPixel)
  7. {
  8.  (y % 2) pPixel[0]=pPixel[1]=pPixel[2] = 0の場合;
  9.  // 奇数行 
  10.  それ以外   
  11. pPixel[0]=pPixel[1]=pPixel[2] = 0xFF;
  12. // 偶数行 
  13. }

次に、次の呼び出しを行います。

  1. PixelHandler (FCPixelTest()、進行状況);

なんと調和がとれていて美しいのでしょう。アルゴリズム設計者は、プログレスバーや領域をどのようにサポートするかを考える必要はなく、独自のアルゴリズムを記述するだけで済みます。よくデザインされたAKのような感じで、弾(オブジェクト)を追加し続けることができます^-^

この時点で、作業は完了しているはずです。まだ質問がありますか?

待ってください、急がないでください。何かがおかしいのです。このアルゴリズムを追加した後、コンパイルになぜこんなに時間がかかるのでしょうか?

問題は、目立たないことにあります。

  1. #include "PixelProcessor.h"  

画像は、画像処理の最も低レベルのオブジェクトです。プロジェクト内のすべてのファイルは、これを直接的または間接的に含みます。したがって、image.h 自体とそれに含まれる .h ファイルを変更すると、ほぼプロジェクト全体がビルドされます。これはもちろん耐え難いことです。解決策は、「前方宣言」を使用することです。PixelHandler インターフェイスでは、その参照のみが必要なためです (つまり、私 (インターフェイス) は、渡されたクラスの内部構造を知る必要はなく、32 (64) のメモリ アドレスのみを渡す必要があります)。

したがって、私たちは

  1. #include "PixelProcessor.h"  

次と置き換えます:

  1. class FCSinglePixelProcessBase; // 外部クラスの前方宣言 

次に、PixelProcessor.h を .cpp ファイルに含めます。この方法では、PixelProcessor.h への変更は .cpp ファイルの再コンパイルのみにつながるため、コンパイル時間が大幅に節約されます。

要約:

1) 可能であれば、プログラミング時に「コードのコピー」について考えないでください。結局のところ、OO は抽象化とコードの再利用のために生まれました。

2) 必要でない限り、クラス メンバー変数と関数パラメーターは、可能な限りポインターまたは参照に置き換える必要があります。これにより、.h に含まれる他の .h ファイルの数が減り、それらを前方宣言に置き換えることができるため、コンパイル時間が短縮され、将来的に相互インクルードされる可能性も減ります。

3) 最後に、効率の問題についてお話ししましょう。ピクセルごとに仮想関数を呼び出すとパフォーマンスに影響が出ると言う人もいるかもしれません。これは事実ですが、実際の損失は想像よりもはるかに小さいです。実際にテストしてみましたが、1024*768 の画像にネガティブ処理を実行すると、速度の低下はわずか 5% 程度です。複雑な処理 (明るさ/コントラスト/ガンマ) を実行する場合、この低下は完全に無視できます。結局のところ、余分なコードはスタック アクセスとテーブル検索だけであり、浮動小数点除算などの時間のかかる命令ではありません。

<<:  SQL SERVER データマイニング: クラスタリングアルゴリズムとシーケンシャルクラスタリングアルゴリズムの理解

>>:  STLコンポーネントアルゴリズム

ブログ    
ブログ    
ブログ    
ブログ    
ブログ    

推薦する

...

...

人工知能が美女を元の姿に戻す方法

誰もが美を愛しますが、誰もが生まれながらに美しさを持っているわけではないので、さまざまな種類の写真美...

文部科学省が文書を発表:AI、アルゴリズム等が2018年度から高等学校の教育課程に取り入れられる!

教育部はこのほど記者会見を開き、「高等学校一般教育課程計画及び中国語等教科教育課程基準(2017年版...

...

タクシー無料!百度:北京の自動運転タクシーサービスが全面オープン

簡単に体験できるものではないため、自動運転技術が実用化にはまだ遠いと感じている人も多いでしょう。しか...

...

よりスケーラブルになるにはどうすればよいでしょうか?

この記事は公開アカウント「Reading Core Technique」(ID: AI_Discov...

...

DrivingGaussian: リアルなサラウンドビューデータ、運転シーンの再構成SOTA

この記事は、Heart of Autonomous Driving の公開アカウントから許可を得て転...

人工知能を活用する準備はできていますか?

[[349302]]今日、職場での学習は課題に直面しています。高度な分析、人工知能、ロボットが職場...

AIを活用した自動化はエンタープライズレベルの自動化2.0です

新たな常態に対応するために自動化プロセスを拡大多くの企業は、ニューノーマルに対処するための重要な技術...

あなたは私の目です!人工知能が障害者にバリアフリーのインターネットアクセスを提供する

この記事は公開アカウント「Reading Core Technique」(ID: AI_Discov...

人工知能に適した9つのプログラミング言語

[[436583]] [51CTO.com クイック翻訳]人工知能という用語は、20 世紀半ばに生ま...

自然の中でショウジョウバエがVRをプレイし、注意メカニズムとワーキングメモリを発見

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...