vtableを使わず関数アドレスを解決させてvirtual functionを呼びだす
通常C++プログラマがvirtual functionを宣言する理由は、virtualメソッドへの関数ポインタが格納されているvtableを暗黙に作り出させ、動的なポリモーフィズムを実現させるためである。
しかし、vtable経由で動的に関数を間接的に呼び出すという事は、コンパイラが最適化を試みる事が難しくなるという事。そんな時に、明示的に指定する事によって、vtable経由ではない呼び出しが可能になる。結果的には、自前で最適化を試みる事と同義だが。
struct A{ virtual const bool f()const{return false;} virtual ~A()=default; }; struct Derived:A{ const bool f()const override{return true;} }; void f() { A* obj=new Derived(); assert(obj->f()); // vtable経由 assert(static_cast<Derived*>(obj)->Derived::f()); // 静的な呼び出し delete obj; }
このコードのvtableの様子。
/// ... 略 Vtable for A A::_ZTV1A: 5u entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI1A) 16 (int (*)(...))A::f 24 (int (*)(...))A::~A 32 (int (*)(...))A::~A Class A size=8 align=8 base size=8 base align=8 A (0x0x101b19360) 0 nearly-empty vptr=((& A::_ZTV1A) + 16u) Vtable for Derived Derived::_ZTV7Derived: 5u entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI7Derived) 16 (int (*)(...))Derived::f 24 (int (*)(...))Derived::~Derived 32 (int (*)(...))Derived::~Derived Class Derived size=8 align=8 base size=8 base align=8 Derived (0x0x142c0b208) 0 nearly-empty vptr=((& Derived::_ZTV7Derived) + 16u) A (0x0x101b19420) 0 nearly-empty primary-for Derived (0x0x142c0b208)
アセンブリ。前者が通常の呼び出し、後者がDerived*にキャストしながらスコープを明示的に指定したモノ。
0029 488B45E8 movq -24(%rbp), %rax 002d 488B00 movq (%rax), %rax 0030 488B00 movq (%rax), %rax 0033 488B55E8 movq -24(%rbp), %rdx 0037 4889D7 movq %rdx, %rdi 003a FFD0 call *%rax
003c 488B45E8 movq -24(%rbp), %rax 0040 4889C7 movq %rax, %rdi 0043 E8000000 call _ZNK7Derived1fEv
しかし、virtual functionを宣言する理由の一つであるポリモーフィズムは、明示的に指定している時点で、当然だが実現できていない。
vtable経由のオーバーヘッドが気になり、かつ型がわかりきっている時に限られる手法だと思う。
因みに文中のvtableの様子を出力させる方法は、gccであれば、-fdump-class-hierarchyオプションを指定する。
% cat test.cpp struct A{ virtual void f(){} virtual void g(){} }; int main(){} % g++ -fdump-class-hierarchy test.cpp % cat test.cpp.002t.class Vtable for A A::_ZTV1A: 4u entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTI1A) 16 (int (*)(...))A::f 24 (int (*)(...))A::g Class A size=8 align=8 base size=8 base align=8 A (0x0x142d626c0) 0 nearly-empty vptr=((& A::_ZTV1A) + 16u)
#追記
元々vtableを使わず、明示的にキャストするのであれば、そもそもオーバーライドさせる必要すらない。
struct X{ void f()const{std::cout<<"X::"<<__func__<<std::endl;} virtual ~X()=default; }; struct Y:X{ void f()const{std::cout<<"Y::"<<__func__<<std::endl;} }; struct Z:X{ void f()const{std::cout<<"Z::"<<__func__<<std::endl;} }; int main() { auto af=[](auto ptr){ ptr->f(); return ptr; }; delete af( new X() ); delete af( static_cast<X*>(new Y()) ); delete af( static_cast<X*>(new Z()) ); }
0010 488B45F0 movq -16(%rbp), %rax 0014 4889C7 movq %rax, %rdi 0017 E8000000 call _ZNK1X1fEv
vtableを作成していないのだから、アセンブリの出力結果は当然と言える。