12

これは簡単な質問かもしれません。私は C++11 テンプレートをまったくマスターしていません。

std::vector<T>パフォーマンス上の理由ではない汎用ベクトル クラスがあります (非常に特殊なコード)。

が POD であるかどうかを確認しT、POD である場合は特別な計算を実行する方が、そうでない場合よりもはるかに効率的であることがわかりました。

void vec<T>::clear() {
  if (!std::is_pod<T>::value) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
  }

  size = 0;
}

Tここでは、 for each itemのデストラクタを呼び出さず(size非常に巨大になる可能性があります)、パフォーマンスが大幅に向上します。しかし、テンプレートがコンパイルされると、テストif (!std::is_pod<T>::value)は役に立たなくなります: にコンパイルされるのではなく:

void vec<int>::clear() {
  if (false) {
    for (int i = 0; i < size; i++) {
       data[i].~int();
    }
  }

  size = 0;
}

私はそれをコンパイルしたい:

void vec<int>::clear() {
  size = 0;
}

if (false)コンパイラは、ブロックやif (true)テストをスキップするのに十分「賢い」ですか? そのコードを多少異なる方法で記述する必要がありますか?

4

3 に答える 3

26

コンパイラは、if (false) ブロックまたは if (true) テストをスキップするのに十分「賢い」ですか?

はい、間違いなく。デッド コードの除去は、日常的に実行される簡単な最適化です。その存在は、多くのデバッグ ライブラリを効率的に動作させるためにも重要です (= リリース モードでのランタイム オーバーヘッドなし)。

しかし、おそらくこれを書き直して、 に基づいて関数をオーバーロードすることにより、これがコンパイル時の区別であることを読者に見えるようにしis_podます。

void vec<T>::do_clear(std::true_type) { }

void vec<T>::do_clear(std::false_type) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
}

void vec<T>::clear() {
    do_clear(std::is_trivially_destructible<T>());
    size = 0;
}

上記のコードでは、コメントでNicolが示唆しているように、コードをより自明にするis_trivially_destructible代わりに使用しています。is_podこの手法は、標準ライブラリの実装やその他のライブラリで一般的に採用されています。これは、タグ ディスパッチと呼ばれます。

于 2013-06-19T09:45:39.493 に答える
10

あなたがやりたいことのために特別に設計された疑似デストラクタと呼ばれる言語機能があります。基本的に、型テンプレート パラメーター T を指定すると、そのデストラクタを構文的に呼び出すことができます。また、インスタンス化されたときに T がスカラー型である場合 (たとえば、 のような基本型であるためint)、コンパイルされ、その中でノーオペレーションが生成されます。場所。

スカラーではない残りの POD 型については、自明なデストラクタがあるため、同様にノーオペレーションが生成されます。

最低の最適化設定であっても、運用コンパイラはノーオペレーションでループを回避します。したがって、安全に書くことができます:

void vec<T>::clear() { 
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }

    size = 0;
}

基本的に、コンパイラがすでに処理している架空のパフォーマンスの問題を解決しようとしています。

于 2013-06-19T12:18:57.013 に答える
2

デッド コードの除去は、一般的な最適化です。

ただし、コンパイラが最適化を行うことをまったく信頼していない場合は、静的 iftemplateライブラリを作成できます。

かなり恐ろしいハックの束を読みたくない場合は、オチまで飛ばしてください。

#include <utility>
#include <type_traits>

template<bool b>
struct static_if_t {
  static_if_t( static_if_t const& ) = default;
  static_if_t() = default;
  static_if_t( static_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};

template<bool dead>
struct static_if_branch {};

template<bool b>
struct static_else_if_t {
  static_else_if_t( static_else_if_t const& ) = default;
  static_else_if_t() = default;
  static_else_if_t( static_else_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};

template<bool b>
static_if_t<b> static_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
template<bool b>
static_else_if_t<b> static_else_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}

static auto static_else = static_else_if<true>;

template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<true> operator*( static_if_t<true>, Lambda&& closure )
{
  std::forward<Lambda>(closure)();
  return {};
}
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<false> operator*( static_if_t<false>, Lambda&& /*closure*/ )
{
  return {};
}

template<typename Unused>
static_if_branch<true> operator*( static_if_branch<true>, Unused&& ) {
  return {};
}

static_if_t< true > operator*( static_if_branch<false>, static_else_if_t<true> ) {
  return {};
}
static_if_t< false > operator*( static_if_branch<false>, static_else_if_t<false> ) {
  return {};
}

そして、ここにオチがあります:

#include <iostream>

int main() {
  static_if<true>* [&]{
    std::cout << "hello\n";
  } *static_else* [&]{
    std::cout << "doom\n";
  };

  static_if<false>* [&]{
    std::cout << "doom the\n";
  } *static_else* [&]{
    std::cout << "world\n";
  };

  static_if<false>* [&]{
    std::cout << "fello\n";
  } *static_else_if<false>* [&]{
    std::cout << "yellow\n";
  } *static_else_if<false>* [&]{
    std::cout << "hehe\n";
  };

  static_if( std::is_same<int, int>() )* [&]{
    std::cout << "int is int\n";
  };
  static_if( std::is_same<double, double>() )* [&]{
    std::cout << "double is double\n";
  } *static_else_if( std::is_same<int, double>() )* [&]{
    std::cout << "int is double\n";
  } *static_else* [&]{
    std::cout << "sky is not blue\n";
  };
}

しかし、なぜそれをしたいのですか? 実際の例

(上記には 2 つの構文があることに注意してくださいstatic_if-- onestatic_if<compile time boolean expression>と another static_if( std::is_whatever<blah>() ))。

さて、上記は完全に正気ではありませんが、上記の手法を使用すると、選択されたブランチに基づいて異なる型を許可するコンパイル時の三項演算子を作成できます。これはきちんとしています。

つまり、次のようなものです。

auto result = trinary<std::is_same<A,B>::value>% 7 | 3.14;

の型はresultintAB同じ型で、double異なる場合です。あるいは:

auto result = meta_trinary<std::is_same<A,B>::value>% [&]{return 7;} | [&]{return 3.14;};

必要に応じて、コードのブロック全体を条件付きで評価し、戻り値の条件付きの型を格納できるようにします。

于 2013-06-19T17:58:48.980 に答える