8

ここSOで同様のトピックに関する回答を調べましたが、満足のいく回答が見つかりませんでした。これはかなり大きなトピックであることを知っているので、より具体的にしようと思います。

ファイルを処理するプログラムを書きたいです。処理は簡単ではないため、さまざまなフェーズをスタンドアロン モジュールに分割し、必要に応じて使用するのが最善の方法です (モジュール A の出力のみに関心がある場合もあれば、他の 5 つのモジュールの出力が必要な場合もあるため)。 )。問題は、あるモジュールの出力が別のモジュールの入力になる可能性があるため、モジュールを連携させる必要があるということです。そして、私はそれが高速である必要があります。さらに、特定の処理を複数回実行することを避けたい (モジュール A がモジュール B および C によって処理される必要があるデータを作成する場合、モジュール B、C の入力を作成するためにモジュール A を 2 回実行したくない) .

モジュールが共有する必要がある情報は、ほとんどの場合、バイナリ データのブロックおよび/または処理されたファイルへのオフセットです。メイン プログラムのタスクは非常に単純です。引数を解析し、必要なモジュールを実行するだけです (そして、おそらく何らかの出力を提供するか、それともモジュールのタスクにする必要がありますか?)。

実行時にモジュールをロードする必要はありません。.h ファイルを含むライブラリを用意し、新しいモジュールまたはモジュールが更新されるたびにプログラムを再コンパイルすることはまったく問題ありません。モジュールの考え方は、主にコードの読みやすさ、保守、および定義済みのインターフェースなどを必要とせずにさまざまなモジュールでより多くの人が作業できるようにするためです(一方で、モジュールの記述方法に関するいくつかの「ガイドライン」モジュールがおそらく必要になるでしょう、私はそれを知っています)。ファイル処理は読み取り専用操作であり、元のファイルは変更されていないと想定できます。

C++ でこれを行う方法について、誰かが私を良い方向に向けることができますか? アドバイスは大歓迎です (リンク、チュートリアル、PDF ブックなど)。

4

3 に答える 3

3

これは、プラグイン アーキテクチャに非常によく似ています。以下を特定するために、(非公式の) データ フロー チャートから始めることをお勧めします。

  • これらのブロックがデータを処理する方法
  • どのデータを転送する必要があるか
  • あるブロックから別のブロックに返される結果 (データ/エラー コード/例外)

これらの情報を使用して、実行時に他のインターフェイスにバインドできる汎用インターフェイスの構築を開始できます。次に、各モジュールにファクトリ関数を追加して、モジュールから実際の処理オブジェクトを要求します。処理オブジェクトをモジュール インターフェイスから直接取得することはお勧めしませんが、処理オブジェクトを取得できるファクトリ オブジェクトを返すことをお勧めします。これらの処理オブジェクトは、処理チェーン全体を構築するために使用されます。

単純化しすぎたアウトラインは次のようになります。

struct Processor
{
    void doSomething(Data);
};

struct Module
{
    string name();
    Processor* getProcessor(WhichDoIWant);
    deleteprocessor(Processor*);
};

私の考えでは、これらのパターンが現れる可能性があります。

  • ファクトリ関数: モジュールからオブジェクトを取得する
  • コンポジット && デコレーター: 処理チェーンの形成
于 2010-05-28T07:51:31.260 に答える
2

C++ がこの目的のために考えるのに適切なレベルかどうか疑問に思っています。私の経験では、UNIX の哲学では、互いにパイプで接続された別個のプログラムを使用することが常に有用であることが証明されています。

データが大きすぎない場合、分割には多くの利点があります。最初に、処理のすべてのフェーズを個別にテストする機能を取得します。1 つのプログラムを実行して、出力をファイルにリダイレクトします。結果を簡単に確認できます。次に、各プログラムがシングル スレッドであっても、複数のコア システムを利用できるため、作成とデバッグがはるかに簡単になります。また、プログラム間のパイプを使用してオペレーティング システムの同期を利用することもできます。また、既存のユーティリティ プログラムを使用してプログラムの一部を実行することもできますか?

最終的なプログラムは、すべてのユーティリティを 1 つのプログラムにまとめる接着剤を作成し、プログラムから別のプログラムにデータをパイプし (この時点ではこれ以上のファイルはありません)、すべての計算で必要に応じてそれを複製します。

于 2010-05-28T07:39:25.287 に答える
2

これは本当に些細なことのように思えるので、いくつかの要件を見逃していると思います。

メモ化を使用して、結果を複数回計算しないようにします。これはフレームワークで行う必要があります。

フローチャートを使用して、あるモジュールから別のモジュールに情報を渡す方法を決定できます...しかし、最も簡単な方法は、各モジュールが依存するモジュールを直接呼び出すことです。メモ化を使用すると、すでに計算されている場合は問題ないため、それほどコストはかかりません。

どのモジュールについても起動できるようにする必要があるため、それらに ID を付与し、実行時に検索できるようにどこかに登録する必要があります。これには 2 つの方法があります。

  • エグザンプラ: この種のモジュールの固有のエグザンプラを取得して実行します。
  • Factory: 要求された種類のモジュールを作成し、実行して破棄します。

このメソッドの欠点はExemplar、モジュールを 2 回実行すると、クリーンな状態から開始するのではなく、最後の (失敗した可能性がある) 実行が残した状態から開始することです。しかし、失敗した場合、結果は計算されません (うーん)。

では、どうすれば... ?

工場から始めましょう。

class Module;
class Result;

class Organizer
{
public:
  void AddModule(std::string id, const Module& module);
  void RemoveModule(const std::string& id);

  const Result* GetResult(const std::string& id) const;

private:
  typedef std::map< std::string, std::shared_ptr<const Module> > ModulesType;
  typedef std::map< std::string, std::shared_ptr<const Result> > ResultsType;

  ModulesType mModules;
  mutable ResultsType mResults; // Memoization
};

これは非常に基本的なインターフェイスです。ただし、(再入の問題を回避するために) を呼び出すたびにモジュールの新しいインスタンスがOrganizer必要になるため、Moduleインターフェイスで作業する必要があります。

class Module
{
public:
  typedef std::auto_ptr<const Result> ResultPointer;

  virtual ~Module() {}               // it's a base class
  virtual Module* Clone() const = 0; // traditional cloning concept

  virtual ResultPointer Execute(const Organizer& organizer) = 0;
}; // class Module

そして今、それは簡単です:

// Organizer implementation
const Result* Organizer::GetResult(const std::string& id)
{
  ResultsType::const_iterator res = mResults.find(id);

  // Memoized ?
  if (res != mResults.end()) return *(it->second);

  // Need to compute it
  // Look module up
  ModulesType::const_iterator mod = mModules.find(id);
  if (mod != mModules.end()) return 0;

  // Create a throw away clone
  std::auto_ptr<Module> module(it->second->Clone());

  // Compute
  std::shared_ptr<const Result> result(module->Execute(*this).release());
  if (!result.get()) return 0;

  // Store result as part of the Memoization thingy
  mResults[id] = result;

  return result.get();
}

簡単なモジュール/結果の例:

struct FooResult: Result { FooResult(int r): mResult(r) {} int mResult; };

struct FooModule: Module
{
  virtual FooModule* Clone() const { return new FooModule(*this); }

  virtual ResultPointer Execute(const Organizer& organizer)
  {
    // check that the file has the correct format
    if(!organizer.GetResult("CheckModule")) return ResultPointer();

    return ResultPointer(new FooResult(42));
  }
};

そしてメインから:

#include "project/organizer.h"
#include "project/foo.h"
#include "project/bar.h"


int main(int argc, char* argv[])
{
  Organizer org;

  org.AddModule("FooModule", FooModule());
  org.AddModule("BarModule", BarModule());

  for (int i = 1; i < argc; ++i)
  {
    const Result* result = org.GetResult(argv[i]);
    if (result) result->print();
    else std::cout << "Error while playing: " << argv[i] << "\n";
  }
  return 0;
}
于 2010-05-28T09:42:03.357 に答える