6

同じソース コードから構築された 3 つの密接に関連するアプリケーションがあります。たとえば、APP_A、APP_B、および APP_C とします。APP_C は、APP_A のスーパーセットである APP_B のスーパーセットです。

これまでのところ、ビルドするアプリケーションを指定するためにプリプロセッサの定義を使用してきました。これは次のように機能します。

// File: app_defines.h
#define APP_A 0
#define APP_B 1
#define APP_C 2

私のIDEビルドオプションは、次に指定します(たとえば)

#define APPLICATION APP_B

...そしてソースコードには、次のようなものがあります

#include "app_defines.h"

#if APPLICATION >= APP_B
// extra features for APPB and APP_C
#endif

しかし、私は今朝自分の足を撃ち、1 つのファイルから #include "app_defines.h" の行を省略しただけで、非常に多くの時間を無駄にしました。すべてが正常にコンパイルされましたが、アプリケーションは起動時に AV でクラッシュしました。

これを処理するためのより良い方法が何であるかを知りたいです。以前は、これは通常、#define を (C++ で) 使用できると考える数少ないケースの 1 つでしたが、それでもひどく間抜けで、コンパイラは私を保護しませんでした。

4

10 に答える 10

12

共通のコード ベースを共有するアプリケーションでは、常に継承関係を強制する必要はありません。本当。

argv[0]、つまりアプリケーション名に基づいてアプリケーションの動作を調整する古い UNIX トリックがあります。私の記憶が正しければ (それを調べてから 20 年も経っています)、rsh と rlogin は同じコマンドでした。argv[0] の値に基づいてランタイム構成を行うだけです。

ビルド構成に固執したい場合は、これが通常使用されるパターンです。ビルド システム/makefile は、APP_CONFIG のようなコマンドのシンボルをゼロ以外の値に定義すると、構成のナットとボルトを含む共通のインクルード ファイルが作成されます。

#define APP_A 1
#define APP_B 2

#ifndef APP_CONFIG
#error "APP_CONFIG needs to be set
#endif

#if APP_CONFIG == APP_A
#define APP_CONFIG_DEFINED
// other defines
#endif

#if APP_CONFIG == APP_B
#define APP_CONFIG_DEFINED
// other defines
#endif

#ifndef APP_CONFIG_DEFINED
#error "Undefined configuration"
#endif

このパターンは、構成がコマンド ラインで定義され、有効であることを強制します。

于 2008-11-04T14:20:49.767 に答える
7

あなたがやろうとしていることは、「製品ライン」に非常に似ているようです。カーニギーメロン大学には、パターンに関する優れたページがあります: http://www.sei.cmu.edu/productlines/

これは基本的に、さまざまな機能を備えた 1 つのソフトウェアのさまざまなバージョンを構築する方法です。Quicken Home/Pro/Business のようなものを想像しているなら、あなたは順調に進んでいます。

それはまさにあなたが試みていることではないかもしれませんが、テクニックは役立つはずです.

于 2008-11-04T13:27:51.043 に答える
2

コードを個別にコンパイルされた要素にモジュール化し、選択した共通モジュールとバリアント固有のトップレベル (メイン) モジュールからバリアントを構築することを検討しているように思えます。

次に、トップ レベルのコンパイルで使用されるヘッダー ファイルと、リンカー フェーズに含める .obj ファイルによって、これらのパーツのどれをビルドに含めるかを制御します。

最初はこれに少し苦労するかもしれません。長期的には、より信頼性が高く、検証可能な構築および保守プロセスが必要です。また、すべての #if バリエーションを気にすることなく、より良いテストを行うことができるはずです。

あなたのアプリケーションがまだそれほど大きくなく、その機能のモジュール化を解明するために大きな泥の玉に対処する必要がないことを願っています。

ある時点で、意図したアプリケーション構成に対して一貫したコンポーネントがビルドで使用されていることを確認するために実行時チェックが必要になる場合がありますが、それは後で調べることができます。コンパイル時の整合性チェックを行うこともできますが、そのほとんどは、ヘッダー ファイルと、特定の組み合わせに入る従属モジュールへのエントリ ポイントのシグネチャで得られます。

これは、C++ クラスを使用しているか、C/C++ 共通言語レベルでほとんど操作しているかに関係なく、同じゲームです。

于 2008-11-04T22:19:11.893 に答える
1

このようなことをします:


CommonApp   +-----   AppExtender                        + = containment
                      ^    ^    ^
                      |    |    |                       ^ = ineritance
                    AppA  AppB  AppC                    |

共通コードをクラスCommonAppに配置し、戦略的な場所でインターフェース「AppExtender」への呼び出しを配置し​​ます。たとえば、AppExtenderインターフェイスには、afterStartup、afterConfigurationRead、beforeExit、getWindowTitleなどの関数があります。

次に、各アプリケーションのメインで、正しいエクステンダーを作成し、それをCommonAppに渡します。


    --- main_a.cpp

    CommonApp application;
    AppA appA;
    application.setExtender(&appA);
    application.run();

    --- main_a.cpp

    CommonApp application;
    AppB appB;
    application.setExtender(&appB);
    application.run();
于 2008-11-04T13:50:44.633 に答える
1

C++ を使用している場合、A、B、および C アプリケーションは共通の祖先から継承するべきではありませんか? それが問題を解決するOOの方法です。

于 2008-11-04T13:12:08.550 に答える
1

問題は、未定義の名前で #if ディレクティブを使用すると、0 として定義されているかのように動作することです。これは、常に最初に #ifdef を実行することで回避できますが、面倒でエラーが発生しやすくなります。

少し良い方法は、名前空間と名前空間のエイリアシングを使用することです。

例えば

namespace AppA {
     // application A specific
}

namespace AppB {
    // application B specific
}

そして、 app_defines.h を使用して名前空間のエイリアシングを行います

#if compiler_option_for_appA
     namespace Application = AppA;
#elif compiler_option_for_appB
     namespace Application = AppB;
#endif

または、より複雑な組み合わせの場合、名前空間のネスト

namespace Application
{
  #if compiler_option_for_appA
     using namespace AppA;
  #elif compiler_option_for_appB
     using namespace AppB;
  #endif
}

または上記の任意の組み合わせ。

利点は、ヘッダーを忘れると、APPLICATION がデフォルトで 0 に設定されているため、コンパイラ iso から不明な名前空間エラーが発生し、サイレントに失敗することです。

そうは言っても、私は同様の状況にありました。すべてを多くのライブラリにリファクタリングすることを選択しました。その大部分は共有コードであり、バージョン管理システムに、定義などに依存するさまざまなアプリケーションのどこに何があるかを処理させました。 . コード内。

私の意見では少しうまく機能しますが、たまたま非常にアプリケーション固有のYMMVであることを認識しています。

于 2008-11-04T13:27:41.637 に答える
1

しかし、私は今朝自分の足を撃ち、1 つのファイルから #include "app_defines.h" の行を省略しただけで、非常に多くの時間を無駄にしました。すべてが正常にコンパイルされましたが、アプリケーションは起動時に AV でクラッシュしました。

この問題には簡単な修正方法があります。警告をオンにして、APP_B が定義されていない場合にプロジェクトがコンパイルされないようにします (または、少なくとも何かが間違っていることがわかるように十分な警告を生成します)。

于 2008-11-04T22:05:31.357 に答える
0

プリプロセッサ定義がいつ定義されているかわからないという特定の技術的問題に対処するには、単純ですが効果的なトリックがあります。

それ以外の -

#define APP_A 0
#define APP_B 1
#define APP_C 2

使用する -

#define APP_A() 0
#define APP_B() 1
#define APP_C() 2

そして、バージョンのクエリが使用する場所で-

#if APPLICATION >= APP_B()
// extra features for APPB and APP_C
#endif

(同じ精神でアプリケーションでも何かをする可能性があります)。

未定義のプリプロセッサ関数を使用しようとすると、ほとんどのコンパイラで警告またはエラーが発生します(一方、未定義のプリプロセッサ定義は単にサイレントに0と評価されます)。ヘッダーが含まれていない場合、特に「警告をエラーとして扱う」場合は、すぐに気付くでしょう。

于 2009-04-30T15:04:40.570 に答える
0

製品ラインの開発をサポートし、構造化された方法で明示的なバリアント管理を促進するツールを検討することをお勧めします。

これらのツールの 1 つは、 pure-systems のpure::variantsです。これは、機能モデルを通じて可変性を管理し、機能がソース コードに実装されているさまざまな場所を追跡できます。

機能モデルから機能の特定のサブセットを選択すると、機能間の制約がチェックされ、製品ラインの具体的なバリアント、つまり特定のソース コード ファイルと定義のセットが作成されます。

于 2008-11-04T13:40:27.307 に答える
0

Alexandrescu の Modern C++ Designを確認してください。彼は、テンプレートを使用したポリシーベースの開発を提示します。基本的に、このアプローチは戦略パターンの拡張であり、すべての選択がコンパイル時に行われるという違いがあります。Alexandrescu のアプローチは、PIMPL イディオムを使用するのと似ていますが、テンプレートを使用して実装していると思います。

共通のヘッダー ファイルで前処理フラグを使用して、コンパイルする実装を選択し、それをコード ベースの他の場所にあるすべてのテンプレートのインスタンス化で使用される型に typedef します。

于 2009-04-30T15:20:48.910 に答える