Vtable alone is not enough. You need to use function poiners and object pointers together to get around type inference. Virtual function is simply a function taking an object pointer as the first parameter (that is self
). So we need to do something like this:
f1(self::Ptr{Void},args...) = ...
f2(self::Ptr{Void},args...) = ...
#then use FunctionWrapper to wrap f1 and f2
mutable struct S
methodtable::MethodList{FunctionPointer}
#add custom function table here, MethodList{FunctionPointer} is a pointer array
#you can also store class property
#other field
end
This is basically how C++ people implement their vtables. Ptr{Void}
is a must to eliminate different types. So you need to use mutable types to take the pointer, then cast it to Ptr{Void}
. You can also use immutable types if they are wrapped in Base.RefValue
(like shared_ptr in C++). And you now don’t have arrays with abstract types, you have array with Ptr{Void}
. To call a specific method, we need to calculate the corresponding offset in method table (in compile time), and invoke that function with the pointer.
This approach actually works in current Julia(I have tried a small prototype in a raytracer project), but programmer needs to create and maintain method table manually. Also, since raw pointer is not tracked by GC, sometimes we need to ensure the lifetime of the object is long enough by using GC.@preserve
.
So the core problem here is that, to use vtable programmer needs to use pointer explicitly everywhere. In C++, self
is an implicit parameter and handled by compiler. Programmer doesn’t need to write out its signature Ptr{Void}
. Also, pointer casting is needed from Ptr{Void} to the concrete types when you use the pointer.This type of thing cannot be abstracted away by designing a vtable library carefully.