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