Cの仮想関数

最近、私は質問を受けました。Cで仮想関数のメカニズムをどのように実装するのでしょうか?



最初は、これを行う方法がわかりませんでした。結局のところ、Cはオブジェクト指向プログラミング言語ではなく、継承というものはありません。 しかし、私はすでにCの経験があり、仮想関数がどのように機能するかを知っていたので、構造(構造)を使用して仮想関数の動作を模倣する方法があるはずだと考えました。



仮想機能とは何かを知らない人のための簡単な説明:

仮想関数は、独自の明確な実装を持つように後継クラスによってオーバーライドできる関数です。 C ++は、仮想関数テーブルなどのメカニズムを使用します

(簡単にvtable)実行時のリンクをサポートするため。 仮想テーブルは、各仮想関数に対して、継承階層内のこの関数の最も近い実装へのポインターを格納する静的配列です。 階層内で最も近い実装は、オブジェクトのメソッドテーブルから関数アドレスを取得することにより、実行時に決定されます。



C ++で仮想関数を使用する簡単な例を見てみましょう



class ClassA { public: ClassA() {data = 10;} virtual void set() { std::cout << "ClassA is increasing" << std::endl; data++; } int get() { set(); return data; } protected: int data; }; class ClassB : public ClassA { public: void set() { std::cout << "ClassB is decreasing" << std::endl; data--; } };
      
      





上記の例では、 get()



set()



2つのメソッドを持つClassA



クラスがあります。 get()



メソッドは仮想関数としてマークされています。 ClassB



クラスでは、その実装が変更されています。 data



整数はprotected



キーワードでマークされているため、 ClassB



下位クラスはそれにアクセスできます。



 int main() { ClassA classA; ClassB classB; std::cout << "ClassA value: " << classA.get() << std::endl; std::cout << "ClassB value: " << classB.get() << std::endl; return 0; }
      
      





結論:



 ClassA is increasing ClassA value: 11 ClassB is decreasing ClassB value: 9
      
      





Cで仮想関数の概念を実装する方法を考えてみましょう。仮想関数はポインターとして表され、vtableに格納され、vtableは静的配列であるため、ClassAクラス自体、ClassAの仮想関数テーブルをシミュレートする構造を作成する必要があります。 ClassAメソッドの実装。



 /*    */ struct ClassA; /*  ,    . */ typedef struct { void (*ClassA)(struct ClassA*); /* "" */ void (*set)(struct ClassA*); /*  set */ int (*get)(struct ClassA*); /*  get */ } ClassA_functiontable; typedef struct ClassA { int data; ClassA_functiontable *vtable; /*    ClassA */ } ClassA; /*   ClassA */ void ClassA_constructor(ClassA *this); void ClassA_set(ClassA *this); int ClassA_get(ClassA *this); /*     ClassA */ ClassA_functiontable ClassA_vtable = {ClassA_constructor, ClassA_set, ClassA_get }; /*   */ void ClassA_constructor(ClassA *this) { this->vtable = &ClassA_vtable; this->data = 10; } void ClassA_set(ClassA *this) { printf("ClassA is increasing\n"); this->data++; } int ClassA_get(ClassA *this) { this->vtable->set(this); return this->data; }
      
      





Cでは、呼び出し元を指すthis



ポインターはありません。 C ++でthis



ポインターの使用をシミュレートするために、 this



ようなパラメーターに名前を付けました(さらに、C ++でのオブジェクトメソッド呼び出しの実際の動作のように見えます)。



上記のコードからわかるように、 ClassA_get()



実装は、vtableからのポインターを介してset()



関数を呼び出します。 次に、派生クラスの実装を見てみましょう。



 /*    */ struct ClassB; /*  ,     ,      */ typedef struct { void (*ClassB)(struct ClassB*); void (*set)(struct ClassB*); void (*get)(struct ClassA*); } ClassB_functiontable; typedef struct ClassB { ClassA inherited_class; } ClassB; void ClassB_constructor(ClassB *this); void ClassB_set(ClassB *this); int ClassB_get(ClassB *this); ClassB_functiontable ClassB_vtable = {ClassB_constructor, ClassB_set, ClassB_get}; void ClassB_constructor(ClassB *this) { /*     */ ClassA_constructor((ClassA*)this); /*          */ this->inherited_class.vtable = (ClassA_functiontable*)&ClassB_vtable; } void ClassB_set(ClassB *this) { printf("ClassB decreasing\n"); this->inherited_class.data--; } int ClassB_get(ClassB *this) { this->inherited_class.vtable->set((ClassA*)this); return this->inherited_class.data; }
      
      





コードからわかるように、vtableを使用してClassBのget()



実装からset()



関数を呼び出します。vtableは目的のset()



関数を指し、「継承された」ClassAクラスを通じて同じ整数data



アクセスします。



これは、 main()



関数の外観です。



 int main() { ClassA classA; ClassB classB; ClassA_constructor(&classA); ClassB_constructor(&classB); printf("ClassA value: %d\n", classA.vtable->get(&classA)); /*   get()  - */ printf("ClassB value: %d\n", classB.inherited_class.vtable->get((struct ClassA*)&classB)); }
      
      





結論:



 ClassA is increasing ClassA value: 11 ClassB decreasing ClassB value: 9
      
      





もちろん、このトリックはC ++や他のオブジェクト指向プログラミング言語のように自然に見えず、Cでプログラムを書いたときにこのようなものを実装する必要はありませんでしたが、それでも内部構造をよりよく理解するのに役立ちます仮想関数。



翻訳者から: C ++での仮想関数の内部実装に関する詳細な記事



All Articles