更新可能な最適化







Microsoftコンパイラでは、クラスを宣言するときに、__ declspec属性に「novtable」拡張機能を追加できます。



記載されている目標は、生成されるコードのサイズを大幅に削減することです。 コンポーネントの実験では、減少はDLLのサイズの0.6から1.2パーセントでした。



適用性:クラスから直接インスタンス化することを意図していないクラス。



例:純粋にインターフェースクラス。



コードでは、次のようになります。



struct __declspec(novtable) IDrawable { virtual void Draw() const = 0; };
      
      





注:無関係な記事の詳細の例を取り除くために、structキーワードを使用してインターフェイスクラスを宣言しました。 一方、クラスを使用する場合は、publicを使用してメソッドの「公開」を示す必要があります。 同じ理由で、この記事ではインターフェイスクラスに仮想デストラクタを追加しません。



「novtable」という名前は、仮想テーブルがないことを約束します...しかし、仮想関数を呼び出すためのメカニズムは、次のコードでどのように機能しますか:



 //   ,   IDrawable: class Rectangle : public IDrawable { virtual void Draw() const override { } int width; int height; }; … IDrawable* drawable = new Rectangle; drawable->Draw(); //   Rectangle::Draw …
      
      







クラスで仮想関数を宣言するときに追加されるものを思い出してください:



  1. 仮想関数のテーブルを定義します。 このテーブルの1つのインスタンスは、クラスのすべてのインスタンスに使用されます。
  2. 仮想関数テーブルへのポインタがクラスデータメンバーに追加されます。
  3. クラスコンストラクターでこのポインターを初期化するコード。


したがって、この例では、IDrawableとRectangleの2つの仮想関数テーブルの宣言があります。 Rectangleオブジェクトを作成すると、IDrawableコンストラクターが最初に実行され、仮想関数テーブルへのポインターが初期化されます。 概略的には、次のようになります。





IDrawableの描画関数は純粋仮想(関数本体の代わりに「= 0」が示されている)として宣言されているため、コンパイラによって生成されたpurecall関数のアドレスは仮想関数のテーブルに書き込まれます。



次に、Rectangleコンストラクターが実行され、同じポインターが初期化されますが、その仮想関数テーブルは次のようになります。







「novtable」とは何をするものであり、マイクロソフトがコードのサイズを縮小すると約束しているのはなぜですか?



Inovable仮想関数テーブルの不必要な定義とIDrawableコンストラクターでのポインターの初期化は、「novtable」を追加するときに結果のコードから除外されます。



この場合、IDrawableを構築するとき、仮想関数テーブルへのポインターには予測できない値が含まれます。 ただし、オブジェクトの完全な構築の前に仮想関数にアクセスする実装を作成するのは通常間違いです。 たとえば、このクラスの非仮想関数が基本クラスのコンストラクターで呼び出され、次に基本関数が仮想関数を呼び出す場合、purecall関数はnovtableなしで呼び出され、novtableで予測不可能な動作が行われます。 どのオプションも受け入れられない場合があります。



サイズの縮小だけでなく、プログラムの加速もあることに注意してください。



RTTI



ご存じのように、std :: dynamic_castを使用すると、クラスの1つのインスタンスからポインターとリンクを、これらのクラスが階層的にリンクされ、ポリモーフィック(仮想関数のテーブルを含む)の場合、別のポインターにキャストできます。 次に、typeid演算子を使用すると、渡されたオブジェクトへのポインター(リンク)を使用して、実行時にオブジェクトに関する情報を取得できます。 これらの機能は、vtableクラスを参照して配置された型情報を使用するRTTIメカニズムによって提供されます。 構造と場所の詳細は、コンパイラによって異なります。 Microsoftコンパイラの場合、次のようになります。







したがって、コンパイル中にRTTIを有効にするようにコンパイラーが命令された場合、novtableはIDrawableのtype_info定義の作成とそれに必要なサービスデータも除外します。

基本クラスへの還元可能なポインター(リンク)が導関数の実装を示すという知識を何らかの方法で提供する場合、std :: static_castはより効率的であり、RTTIを必要としません。



マイクロソフト固有



MSVCに加えて、同じ構文のこの機能は、WindowsでコンパイルするときにClangに存在します。



結論



  1. __declspec(novtable)-クラスインスタンスが占有するメモリ量には影響しません。
  2. 未使用の仮想関数テーブルの定義、RTTIオーバーヘッド、およびインターフェイスクラスコンストラクターの仮想関数テーブルへのポインターの初期化コードを削除することにより、サイズの縮小とプログラムの高速化が保証されます。



All Articles