web-dev-qa-db-ja.com

コンパイル時の構成可能なクラス機能のC ++パターン

リソースの制限(組み込み)のため、クラスが提供するいくつかの機能をオプションにしたいと思います。このオプション機能を含めるかどうかの決定は、コンパイル時に行う必要があります。

たとえば、単純なプールベースのメモリアロケータがあり、オプションの統計サポートがあります。統計サポートは、x86およびARMデバッグビルドでは利用できますが、ARMリリースビルドでは利用できません。

次のコードは、現在のCベースの実装からの抜粋であり、プール自体と条件付きで含まれる統計メンバー構造間の相互作用の種類を示しています。

struct fmpool {
  // ...
  #ifdef FMPOOL_SUPPORT_STATISTICS
  fmpool_statistics stats;
  #endif
  // ...
}

int fmpool_alloc(fmpool *pool, void **alloc_block, size_t alloc_size)
{

  // Do the allocation

  #ifdef FMPOOL_SUPPORT_STATISTICS
  selected_pool->stats.blocks_used++;
  if (selected_pool->stats.blocks_used > selected_pool->stats.blocks_used_peak)
  {
    selected_pool->stats.blocks_used_peak = selected_pool->stats.blocks_used;
  }
  debug_log_pool_info(selected_pool);
  #endif

  // ...
}

どのパターンがこのユースケースに最適であるかはわかりませんが、アロケーター自体から統計コードを削除して、アロケーターのコードを整理します。

さらに、コードは#ifdefsでひどく雑然としているため、アロケータコードとアロケータ統計コードの間の相互作用をできるだけ減らすことが望ましいです。

3

_#ifdef_への継続は、C++では完全に問題ありません。これらの種類の機能トグルは、プリプロセッサに適しています。

または、オプションの機能を別の構造体/クラスに抽出し、コンパイル時のテンプレートまたは実行時のポリモーフィズムを使用して、正しい実装を選択することもできます。ここで、最も近いのはテンプレートを定義することです。それは次のようなものになります:

_template<class Statistics>
struct fmpool {
  // ...
  Statistics stats {};
  // ...
};

template<class Statistics>
int fmpool_alloc(fmpool<Statistics>& pool, void **alloc_block, size_t alloc_size)
{

  // Do the allocation

  selected_pool.stats.event_allocation(selected_pool);

  // ...
}
_

次に、選択できる統計タイプを定義します。統計が無効になっている場合は、何もしないnullオブジェクトを指定します。

_class StatisticsDisabled {
  template<class Pool>
  void event_allocation(Pool& pool) { /* nop */ }
};

struct StatisticsEnabled {
  unsigned blocks_used;
  unsigned blocks_used_peak;

  template<class Pool>
  void event_allocation(Pool& pool) {
    blocks_used++;
    if (blocks_used > blocks_used_peak)
    {
      blocks_used_peak = blocks_used;
    }
    debug_log_pool_info(pool);
  }
};
_

最後に、fmpoolは、正しい_#ifdef_を使用して、正しい統計でインスタンス化できます。

_#ifdef FMPOOL_SUPPORT_STATISTICS
  using Pool = fmpool<StatisticsEnabled>;
#else
  using Pool = fmpool<StatisticsDisabled>;
#endif

Pool mypool;
_

これらのテンプレートのオーバーヘッドは何ですか?

  • 元のマクロと同様に、実行時のオーバーヘッドはありません。コンパイラーは、統計オブジェクトへの関数呼び出しを最適化できる必要があります。テンプレート関数は暗黙的にinlineです。つまり、canがインライン化されます。 willがインライン化されるように強制するには、コンパイラ固有のアノテーションを使用する必要があります。

  • ただし、コンパイル時のオーバーヘッドがあります。プールを使用するコードもテンプレート化する必要があり、プールの実装はヘッダーで行う必要があります。結果として、小規模でないプロジェクトではコンパイル時間が著しく低下します。

代替案は何ですか?

  • ポリモーフィズムには実行時のオーバーヘッドがありますが、テンプレートを回避できます。ここでは、StatisticsEnabledおよびStatisticsDisabled型によって継承される追加の統計インターフェイスstruct Statistics { virtual void event_allocation(fmpool&) = 0; }を定義する必要があります。プールには、どちらかのタイプを保持する_std::unique_ptr<Statistics> stats_メンバーが含まれます。コンストラクターで正しい実装を提供できます。

  • ここでは、テンプレートソリューションの構造を維持しながら、テンプレートを削除できます。代わりに、条件付きコンパイルによって正しいメンバータイプが直接選択されます。

    _struct fmpool {
      // ...
      #ifdef FMPOOL_SUPPORT_STATISTICS
        StatisticsEnabled stats;
      #else
        StatisticsDisabled stats;
      #endif
      // ...
    };
    _

    テンプレートの場合と同じランタイムオーバーヘッドの考慮事項が適用されます。統計クラスの関数がinlineまたは内部リンケージがあると仮定すると、コンパイラーは統計クラスを導入するオーバーヘッドを完全に削除できるはずです。内部リンケージを強制する最新のC++の方法は、それらを匿名の_namespace { }_に入れることです。これには、fmpool定義がヘッダーではなくコンパイル単位の内部にある必要があることに注意してください。

4
amon