web-dev-qa-db-ja.com

リスト内のオブジェクトの表示と処理?

私は現在、グリッド内のいくつかの条件を満たすいくつかのオブジェクトを表示するために、次のようなコードを持っています。

// simplified
void MyDialog::OnTimer()
{
    UpdateDisplay();
}

void MyDialog::UpdateDisplay()
{
    std::list<MyClass *> objects;
    someService_->GetObjectsMeetingCriteria(objects, sortField, sortDirection);

    for(std::list<MyClass *>::iterator it = objects.begin();
        it != objects.end() ; it ++)
    {
        AddObjectToGrid(*it);
    }

}   

これは正常に機能します。これらのオブジェクトがいくつかの基準を満たしたときに、これらのオブジェクトを処理する必要が生じました。オブジェクトの情報はすぐに変更される可能性があるため、オブジェクトが基準を満たしているかどうかを確認し、すぐに処理してこの方法で続行することが最も望ましいでしょう。

私の質問は、表示と処理を処理するためにこれをどのように最適に設計するかです。次のようなオブジェクトを処理するメソッドを追加するだけです。

for(std::list<MyClass *>::iterator it = objects.begin();
    it != objects.end() ; it ++)
{
    ProcessObject(*it);
    AddObjectToGrid(*it);
}

ただし、理想的には、オブジェクトが処理の直前に基準を満たしているかどうかをチェックして、最新の情報に基づいて動作することを確認します。この例では、すべてのオブジェクトが基準に一致するかどうかがチェックされ、その後、各オブジェクトが処理されます。

また、これが処理コードと表示コードを結合しているのではないかと心配していますが、それらを分離する方法や、それが必要かどうかはわかりません。私はこのようにそれを解決することができます:

void MyDialog::OnTimer()
{
    ProcessObjects();
    UpdateDisplay();
}

しかし、オブジェクトのリストを2回繰り返しています。1回は処理用、もう1回は表示用です。

最後に、次のようなことができます。

// simplified
void MyDialog::OnTimer()
{
    ProcessAndDisplayObjects();
}

void MyDialog::ProcessAndDisplayObjects()
{
    std::list<MyClass *> objects;
    someService_->GetAll(objects, sortField, sortDirection);

    for(std::list<MyClass *>::iterator it = objects.begin();
        it != objects.end() ; it ++)
    {
        if(someService->MeetsCriteria(*it))
        {
          ProcessObject(*it);
          AddObjectToGrid(*it);
        }
    }
}   

全体的に私を悩ませているのは、タイムリーな処理が重要であるため、表示コードと処理コードを結合し、コードを効率的に実行することを心配しています。このコードを最適に構成するにはどうすればよいですか?

3
User

最初に理想について説明し、次に制約の範囲内で十分に説明します。理想的には、処理コードをダイアログクラスの外部に配置し、GUIスレッド以外のスレッドで個別に処理を実行して、データが変更されたときに処理が必要になったときに処理コードに通知を受け取り、通知するようにします。タイマーを使用するのではなく、データを更新するためのダイアログ。 (タイマーには、細かすぎる、または細かすぎる、多すぎる、サイクルを不必要に消費する、不十分である、GUIが応答しないという問題があります。)

さて、あなたはおそらく別々のスレッドと通知を行う準備ができていませんが、それでもダイアログの外に処理コードを移動することは重要です。 2つのプロセスを結合することについてのあなたの懸念は、今のところ些細な問題であることが判明したとしても、要件とコードの変更は十分に根拠があります。これは、それらを分離するための最良のアプローチであり、上記の他の機能を後で追加するのがはるかに簡単になります。重要なことは、タイマーが処理と更新の両方を生成する場合でも、更新ができる限り実行しないことです。つまり、グリッドに配置する値を取得するために関数を呼び出すだけです。処理はすべて、開始時までに完了する必要があります。コードに他の構造上の変更がないと仮定すると、次のようなものを使用します。

_OnTimer()
{
  if (!processor.IsReady())
    processor.process();
  UpdateDisplay()
}
_

後で、process()呼び出し用に新しいスレッドを生成する場合は、processthread.start()の代わりにprocessor.process();などを呼び出して戻り、次のタイマーサイクルを許可することで簡単に実行できます。データを取得します。プロセスルーチンを起動する別の方法(データが変更されたことを通知するもの、2番目のタイマーなど)が必要な場合は、上記を次のように変更できます。

_OnTimer()
{
  if (processor.IsReady())
    UpdateDisplay();
}
_

データセットが本当に巨大でない限り、2回繰り返すことを心配する必要はありません。また、心配するほど巨大な場合は、GUIスレッドの外部で実行することを検討する理由がさらにあります。これは、どのように因数分解しても、ディスプレイが機能しなくなるためです。プロセッサオブジェクトがインターフェイスの背後にある場合は、ダイアログコードに影響を与えることなく、後でリファクタリングできます。また、OnTimer()にコードを追加してフラグをチェックし、データが処理されているかどうかだけでなく、前回の更新以降に変更されているかどうかを確認し、変更されていない場合はスキップできます。

1
kylben

あなたは(少なくとも明らかに)C++を使用しているので、標準のイテレーターとアルゴリズムの動作にもう少し近づけるようにコードを記述しようと思います。まず、「グリッド」にイテレータインターフェイスを提供するための小さなファサードから始めます。

// warning: untested code.
struct grid_proxy { 
    grid_proxy &operator=(MyClass const &item) { 
        AddObjectToGrid(item);
        return *this;
    }
};

class grid_iterator : public std::iterator<std::output_iterator_tag, MyClass> { 
public:
    operator*() { return grid_proxy(); }
    grid_iterator operator++() { return *this; }
    grid_iterator operator++(int) { return *this; }
};

次に、おそらくライブラリにあるはずの小さなアルゴリズムを記述しますが、そうではありません:a transform_if

template <class InputIterator, class OutputIterator, class UnaryOperation, class Predicate>
void transform_if(InputIterator start, InputIterator stop, 
                  OutputIterator out, 
                  UnaryOperation op, 
                  Predicate pred) 
{
    while (start != stop) {
        if (pred(*start))
            *out++ = op(*start);
        ++start;
    }
}

これらを使用すると、全体的な操作は次のようになります。

std::list<MyClass *> objects;
someService_->GetAll(objects, sortField, sortDirection);

transform_if(objects.begin(), objects.end(), grid_iterator(), MeetsCriteria());

現時点では、MeetsCriteriaはメンバー関数ではなくファンクターとして記述されると想定しています。 C++ 11機能を使用できる場合は、代わりにラムダの使用を検討することをお勧めします。

いずれにせよ、これはほとんどのコードのかなり公平な分離を提供します。 transform_ifは、イテレータに書き込んでいることを単に知っています。grid_iteratorだけが、AddObjectToGridが関係しているという手がかりを持っています。イテレータが異なれば、出力の生成もまったく異なります。

同様に、transform_ifは、適切な署名を持つ関数のように呼び出すことができる限り、基準やその決定方法についてはまったく気にしません。

これは、適合イテレータ型などを作成する際に少しオーバーヘッドを課しますが、いくつかの利点があります。前述のデカップリングは確かに便利です。 C++ライブラリをかなりよく知っている人なら誰でも、ほとんど何も考えずに何が起こっているのかを知っているという事実も、原則として非常に良いことです。

1
Jerry Coffin