12

基本クラス インターフェイスと dynamic_cast と非仮想関数の呼び出しを使用して、仮想関数の平均呼び出し時間を見積もる簡単な例を作成しました。はい、これ:

#include <iostream>
#include <numeric>
#include <list>
#include <time.h>

#define CALL_COUNTER (3000)

__forceinline int someFunction()
{
  return 5;
}

struct Base
{
  virtual int virtualCall() = 0;
  virtual ~Base(){};
};

struct Derived : public Base
{
  Derived(){};
  virtual ~Derived(){};
  virtual int virtualCall(){ return someFunction(); };
  int notVirtualCall(){ return someFunction(); };
};


struct Derived2 : public Base
{
  Derived2(){};
  virtual ~Derived2(){};
  virtual int virtualCall(){ return someFunction(); };
  int notVirtualCall(){ return someFunction(); };
};

typedef std::list<double> Timings;

Base* createObject(int i)
{
  if(i % 2 > 0)
    return new Derived(); 
  else 
    return new Derived2(); 
}

void callDynamiccast(Timings& stat)
{
  for(unsigned i = 0; i < CALL_COUNTER; ++i)
  {
    Base* ptr = createObject(i);

    clock_t startTime = clock();

    for(int j = 0; j < CALL_COUNTER; ++j)
    {
      Derived* x = (dynamic_cast<Derived*>(ptr));
      if(x) x->notVirtualCall();
    }

    clock_t endTime = clock();
    double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
    stat.push_back(callTime);

    delete ptr;
  }
}

void callVirtual(Timings& stat)
{
  for(unsigned i = 0; i < CALL_COUNTER; ++i)
  {
    Base* ptr = createObject(i);

    clock_t startTime = clock();

    for(int j = 0; j < CALL_COUNTER; ++j)
      ptr->virtualCall();


    clock_t endTime = clock();
    double callTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
    stat.push_back(callTime);

     delete ptr;
  }
}

int main()
{
  double averageTime = 0;
  Timings timings;


  timings.clear();
  callDynamiccast(timings);
  averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0);
  averageTime /= timings.size();
  std::cout << "time for callDynamiccast: " << averageTime << std::endl;

  timings.clear();
  callVirtual(timings);
  averageTime = (double) std::accumulate<Timings::iterator, double>(timings.begin(), timings.end(), 0);
  averageTime /= timings.size();
  std::cout << "time for callVirtual: " << averageTime << std::endl;

  return 0;
}

callDynamiccast にはほぼ 2 倍の時間がかかるようです。

time for callDynamiccast: 0.000240333

time for callVirtual: 0.0001401

なぜそうするのですか?

編集済み: オブジェクトの作成は別の関数で行われるようになったため、コンパイラは実際の型を認識しません。ほぼ同じ結果です。

EDITED2: 2 つの異なるタイプの派生オブジェクトを作成します。

4

4 に答える 4

19

仮想関数呼び出しは、関数ポインターに似ています。または、コンパイラーが型を認識している場合は、静的ディスパッチです。これは一定時間です。

dynamic_castはまったく異なります。実装定義の手段を使用して型を決定します。時間は一定ではなく、クラス階層をトラバースし (多重継承も考慮してください)、いくつかのルックアップを実行する場合があります。実装では、文字列比較を使用できます。したがって、複雑さは 2 次元で高くなります。リアルタイム システムは、多くの場合dynamic_cast、これらの理由で回避または阻止します。

詳細については、このドキュメントを参照してください。

于 2012-03-31T20:58:54.897 に答える
10

仮想関数の全体的な目的は、継承グラフをキャストダウンする必要がないことに注意する必要があります。派生クラスインスタンスを基本クラスであるかのように使用できるように、仮想関数が存在します。これにより、関数のより特殊な実装を、元々基本クラスバージョンと呼ばれていたコードから呼び出すことができます。

仮想関数が派生クラス+関数呼び出しへの安全なキャストよりも遅い場合、C++コンパイラは単純に仮想関数呼び出しをそのように実装します。

dynamic_castしたがって、 +callが高速になると期待する理由はありません。

于 2012-03-31T21:31:18.093 に答える
5

のコストを測定しているだけですdynamic_cast<>。RTTI を使用して実装されますが、これは C++ コンパイラではオプションです。プロジェクト + プロパティ、C/C++、言語、ランタイム型情報設定を有効にします。いいえに変更します。

dynamic_cast<>これで、適切な仕事を行うことができなくなった微妙なリマインダーが表示されます。static_cast<>大幅に異なる結果を得るには、任意に変更してください。ここでの重要なポイントは、アップキャストが常に安全であることがわかっstatic_cast<>ている場合、探しているパフォーマンスを購入できるということです。アップキャストが安全であるという事実を知らない場合は、問題を回避dynamic_cast<>できます。これは、非常に診断が難しい種類のトラブルです。一般的な障害モードはヒープの破損です。本当に幸運な場合にのみ、すぐに GPF を取得できます。

于 2012-03-31T21:25:52.107 に答える
2

違いは、から派生した任意のインスタンスで仮想関数を呼び出すことができることですBasenotVirtualCall()メンバーが 内に存在しないため、Baseオブジェクトの正確な動的タイプを最初に判別しないと呼び出すことができません。

この違いの結果として、基本クラスの vtable には、virtualCall()呼び出す正しい関数への関数ポインタを含む のスロットが含まれます。したがって、仮想呼び出しは、タイプ のすべてのオブジェクトの最初の (非表示の) メンバーとして含まれる vtable ポインターを単純に追跡し、 にBase対応するスロットからポインターをロードし、virtualCall()そのポインターの背後にある関数を呼び出します。

dynamic_cast<>対照的に、クラスはBase、コンパイル時に他のクラスが最終的にどのクラスから派生するかを知りません。したがって、その vtable 内に の解決を容易にする情報を含めることはできませんdynamic_cast<>dynamic_cast<>これは、仮想関数呼び出しよりも実装コストが高くなる情報の欠如です。実際のdynamic_cast<>オブジェクトの継承ツリーを実際に検索して、キャストの宛先タイプがそのベース内にあるかどうかを確認する必要があります。それは、仮想呼び出しが回避する作業です。

于 2017-04-04T13:28:16.333 に答える