Abstract classes vs interfaces, casting from void*

John Colvin john.loughran.colvin at gmail.com
Sat Aug 10 08:18:05 UTC 2019


On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
> We're getting into somewhat advanced topics now. This is 
> described in the Application Binary Interface page of the 
> documentation[0]. In short: classes and interfaces both use a 
> vtable[1] that holds pointers to each of their methods. When we 
> cast a class instance to an interface, the pointer is adjusted, 
> such that the interface's vtable is the first member. Casting 
> via `void*` bypasses this adjustment.
>
> Using `__traits(classInstanceSize)`, we can see that `C` has a 
> size of 12 bytes, while `D` only is 8 bytes (24 and 16 on 
> 64-bit). This corresponds to the extra interface vtable as 
> described above.
>
> When we first cast to `void*`, no adjustment happens, because 
> we're not casting to an interface. When we later cast the 
> `void*` to an interface, again no adjustment happens - in this 
> case because the compiler doesn't know what we're casting from.
>
> If we use `__traits(allMembers, C)`, we can figure out which 
> methods it actually has, and implement those with some extra 
> debug facilities (printf):
>
> class C : I
> {
>     override void foo() { writeln("hi"); }
>     override string toString()       { writeln("toString"); 
> return ""; }
>     override hash_t toHash()         { debug printf("toHash"); 
> return 0; }
>     override int opCmp(Object o)     { writeln("opCmp"); return 
> 0; }
>     override bool opEquals(Object o) { writeln("opEquals"); 
> return false; }
> }
>
> If we substitute the above in your program, we see that the 
> `toString` method is the one being called. This is simply 
> because it's at the same location in the vtable as `foo` is in 
> `I`'s vtable.
>
> When casting from a class to a superclass, no pointer 
> adjustment is needed, as the vtable location is the same for 
> both.
>
> We can look closer at the vtable, and see that for a new 
> subclass, additional entries are simply appended at the end:
>
> class C {
>     void foo() {}
> }
>
> class D : C {
>     void bar() {}
> }
>
> unittest {
>     import std.stdio;
>
>     C c = new C();
>     D d = new D();
>
>     writeln("Pointer to foo(): ", (&c.foo).funcptr);
>     writeln("Pointer to bar(): ", (&d.bar).funcptr);
>
>     writeln("Pointer to foo() in C's vtable: ", c.__vptr[5]);
>
>     writeln("Pointer to foo() in D's vtable: ", d.__vptr[5]);
>     writeln("Pointer to bar() in D's vtable: ", d.__vptr[6]);
> }
>
> As we see, `foo()` has the position in the vtable for both `c` 
> and `d`, while `D`'s new `bar()` method is added as the next 
> entry.
>
> --
>   Simen
>
> [0]: https://dlang.org/spec/abi.html
> [1]: https://en.wikipedia.org/wiki/Virtual_method_table

Thanks for the extra detail.

Is there a solid reason to ever use an interface over an abstract 
class? (Other than multiple inheritance).

I'm such a noob at anything related to OO.


More information about the Digitalmars-d-learn mailing list