www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Abstract classes vs interfaces, casting from void*

reply John Colvin <john.loughran.colvin gmail.com> writes:
import std.stdio;

interface I
{
     void foo();
}

class C : I
{
     override void foo() { writeln("hi"); }
}

abstract class AC
{
     void foo();
}

class D : AC
{
     override void foo() { writeln("hi"); }
}

void main()
{
     auto c = new C();
     writeln(0);
     (cast(I)cast(void*)c).foo();
     writeln(1);
     (cast(C)cast(void*)c).foo();
     writeln(2);
     (cast(I)cast(C)cast(void*)c).foo();

     auto d = new D();
     writeln(3);
     (cast(AC)cast(void*)d).foo();
     writeln(4);
     (cast(D)cast(void*)d).foo();
     writeln(5);
     (cast(AC)cast(D)cast(void*)d).foo();
}

This produces the output:

0
1
hi
2
hi
3
hi
4
hi
5
hi

Why is there no "hi" between 0 and 1?
Aug 09 2019
next sibling parent reply kinke <kinke gmx.net> writes:
On Friday, 9 August 2019 at 12:26:59 UTC, John Colvin wrote:
 Why is there no "hi" between 0 and 1?
Because you are treating the unadjusted object pointer as interface pointer and then call the only virtual function of that interface, in the 2nd vtbl slot (after the TypeInfo ptr). Casting a class ref to an interface offsets the pointer, so that the interface ref points to the interface vptr for that object instance. This is missing in that line, and so you are invoking the first virtual function of class C, which is some base function in `Object`.
Aug 09 2019
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Friday, 9 August 2019 at 13:19:14 UTC, kinke wrote:
 On Friday, 9 August 2019 at 12:26:59 UTC, John Colvin wrote:
 Why is there no "hi" between 0 and 1?
Because you are treating the unadjusted object pointer as interface pointer and then call the only virtual function of that interface, in the 2nd vtbl slot (after the TypeInfo ptr). Casting a class ref to an interface offsets the pointer, so that the interface ref points to the interface vptr for that object instance. This is missing in that line, and so you are invoking the first virtual function of class C, which is some base function in `Object`.
Ok, makes sense, thanks.
Aug 09 2019
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 9 August 2019 at 12:26:59 UTC, John Colvin wrote:
 import std.stdio;

 interface I
 {
     void foo();
 }

 class C : I
 {
     override void foo() { writeln("hi"); }
 }

 abstract class AC
 {
     void foo();
 }

 class D : AC
 {
     override void foo() { writeln("hi"); }
 }

 void main()
 {
     auto c = new C();
     writeln(0);
     (cast(I)cast(void*)c).foo();
     writeln(1);
     (cast(C)cast(void*)c).foo();
     writeln(2);
     (cast(I)cast(C)cast(void*)c).foo();

     auto d = new D();
     writeln(3);
     (cast(AC)cast(void*)d).foo();
     writeln(4);
     (cast(D)cast(void*)d).foo();
     writeln(5);
     (cast(AC)cast(D)cast(void*)d).foo();
 }

 This produces the output:

 0
 1
 hi
 2
 hi
 3
 hi
 4
 hi
 5
 hi

 Why is there no "hi" between 0 and 1?
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
Aug 09 2019
next sibling parent John Colvin <john.loughran.colvin gmail.com> writes:
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.
Aug 10 2019
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
Aug 10 2019
next sibling parent reply Antonio Corbi <antonio ggmail.com> writes:
On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
Hi John. One reason could be data. Abstract classes can hold data, interfaces can't. Antonio
Aug 10 2019
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Saturday, 10 August 2019 at 10:02:02 UTC, Antonio Corbi wrote:
 On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
Hi John. One reason could be data. Abstract classes can hold data, interfaces can't. Antonio
That's a reason to use an abstract class, not a reason to use an interface.
Aug 10 2019
prev sibling next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
The general question is tricky, as different languages differ in details what is forced and what is allowed for abstract classes and interfaces. But roughly speaking, my opinion is: if you can/want to provide some default behavior than you are about to write an abstract class. If you are about to provide information/restriction of behavior, then this is more like an interface.
Aug 10 2019
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Saturday, 10 August 2019 at 10:11:15 UTC, Alex wrote:
 On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
The general question is tricky, as different languages differ in details what is forced and what is allowed for abstract classes and interfaces. But roughly speaking, my opinion is: if you can/want to provide some default behavior than you are about to write an abstract class. If you are about to provide information/restriction of behavior, then this is more like an interface.
Ok. What would go wrong (in D) if I just replaced every interface with an abstract class?
Aug 10 2019
next sibling parent reply Alex <sascha.orlov gmail.com> writes:
On Saturday, 10 August 2019 at 14:29:03 UTC, John Colvin wrote:
 On Saturday, 10 August 2019 at 10:11:15 UTC, Alex wrote:
 On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
The general question is tricky, as different languages differ in details what is forced and what is allowed for abstract classes and interfaces. But roughly speaking, my opinion is: if you can/want to provide some default behavior than you are about to write an abstract class. If you are about to provide information/restriction of behavior, then this is more like an interface.
Ok. What would go wrong (in D) if I just replaced every interface with an abstract class?
´´´ void main(){} interface A { void fun(); } abstract class B{ void fun(); } class C : A{ void fun(){} } class D : B{ /*override*/ void fun(){} } ´´´ case 1: interface A and class C implementing interface A: You don't need to "override" anything. You are forced to provide an implementation of the function inside the class. case 2: abstract class B and class D inheriting from it: You can but not have to provide an implementation of a function inside the abstract class. If I don't and do not provide any implementation inside D I get a linker error. Don't how this case behaves on your system. If you provide an implementation inside the abstract class, you don't have to provide any in the derived one. In any case, if you want to provide an implementation inside the derived class you have to literally "override", as in D implicit overrides are not allowed.
Aug 10 2019
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Saturday, 10 August 2019 at 17:28:32 UTC, Alex wrote:
 ´´´
 void main(){}
 interface A { void fun(); }
 abstract class B{ void fun(); }
 class C : A{ void fun(){} }
 class D : B{ /*override*/ void fun(){} }
 ´´´

 case 1:
 interface A and class C implementing interface A:
 You don't need to "override" anything. You are forced to 
 provide an implementation of the function inside the class.

 case 2:
 abstract class B and class D inheriting from it:
 You can but not have to provide an implementation of a function 
 inside the abstract class.
 If I don't and do not provide any implementation inside D I get 
 a linker error. Don't how this case behaves on your system.
 If you provide an implementation inside the abstract class, you 
 don't have to provide any in the derived one.
 In any case, if you want to provide an implementation inside 
 the derived class you have to literally "override", as in D 
 implicit overrides are not allowed.
I think there's some confusion here, because B.foo is not abstract. abstract on a class is not inherited by its methods. https://dlang.org/spec/attribute.html#abstract
Aug 11 2019
parent reply Alex <sascha.orlov gmail.com> writes:
On Sunday, 11 August 2019 at 13:09:43 UTC, John Colvin wrote:
 Ok. What would go wrong (in D) if I just replaced every 
 interface with an abstract class?
I think there's some confusion here, because B.foo is not abstract. abstract on a class is not inherited by its methods. https://dlang.org/spec/attribute.html#abstract
Now, I'm confused, as you asked about abstract classes. So, yes, you can define the abstractness of classes differently. And what is your point?
Aug 11 2019
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Sunday, 11 August 2019 at 15:16:03 UTC, Alex wrote:
 On Sunday, 11 August 2019 at 13:09:43 UTC, John Colvin wrote:
 Ok. What would go wrong (in D) if I just replaced every 
 interface with an abstract class?
I think there's some confusion here, because B.foo is not abstract. abstract on a class is not inherited by its methods. https://dlang.org/spec/attribute.html#abstract
Now, I'm confused, as you asked about abstract classes. So, yes, you can define the abstractness of classes differently. And what is your point?
I'm trying to narrow down exactly what patterns work with each and how they overlap. What I was trying to get at with the abstract method thing is that abstract class C { void foo(); } is an abstract class with a non-abstract method, whose implementation is going to come from somewhere else (not a common pattern in D). class C { abstract void foo(); } is an abstract class with an abstract method foo, which means you have to override it in a inheriting class to get a non-abstract class.
Aug 11 2019
parent reply Alex <sascha.orlov gmail.com> writes:
On Sunday, 11 August 2019 at 16:05:20 UTC, John Colvin wrote:
 I'm trying to narrow down exactly what patterns work with each 
 and how they overlap.

 What I was trying to get at with the abstract method thing is 
 that

 abstract class C
 {
     void foo();
 }

 is an abstract class with a non-abstract method, whose 
 implementation is going to come from somewhere else (not a 
 common pattern in D).

 class C
 {
     abstract void foo();
 }

 is an abstract class with an abstract method foo, which means 
 you have to override it in a inheriting class to get a 
 non-abstract class.
As I see this, everything you wrote is correct. :) But you compared abstractness with interface usage, initially. So... I would say, interfaces are more like the abstract method case without any function body. But then, you will have to use "override" all across the inherited classes.
Aug 11 2019
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Sunday, 11 August 2019 at 20:15:34 UTC, Alex wrote:
 On Sunday, 11 August 2019 at 16:05:20 UTC, John Colvin wrote:
 I'm trying to narrow down exactly what patterns work with each 
 and how they overlap.

 What I was trying to get at with the abstract method thing is 
 that

 abstract class C
 {
     void foo();
 }

 is an abstract class with a non-abstract method, whose 
 implementation is going to come from somewhere else (not a 
 common pattern in D).

 class C
 {
     abstract void foo();
 }

 is an abstract class with an abstract method foo, which means 
 you have to override it in a inheriting class to get a 
 non-abstract class.
As I see this, everything you wrote is correct. :) But you compared abstractness with interface usage, initially. So... I would say, interfaces are more like the abstract method case without any function body. But then, you will have to use "override" all across the inherited classes.
Ok. So that means the difference is pretty subtle, give or take a few extra keywords. Which leaves multiple inheritance as the only significant difference? From my perspective it looks like there are two massively overlapping features with some quite arbitrary feeling restrictions and differences. E.g. why can I not inherit from multiple 100% abstract empty classes? Wouldn't that be the same as inheriting from multiple interfaces?
Aug 11 2019
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 11 August 2019 at 20:32:14 UTC, John Colvin wrote:
 E.g. why can I not inherit from multiple 100% abstract empty 
 classes? Wouldn't that be the same as inheriting from multiple 
 interfaces?
There's kinda no such thing as 100% empty abstract classes, since they all have the implicit parent of Object with its associated pieces. D's interfaces have no implicit parent. But if that were to change, then yeah, it should work - that's basically what C++ does in lieu of interfaces. (I personally prefer separate interfaces anyway, as it makes the intention clear and thus can help with better error messages and documentation, but it would work.)
Aug 11 2019
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, August 11, 2019 2:32:14 PM MDT John Colvin via Digitalmars-d-
learn wrote:
 On Sunday, 11 August 2019 at 20:15:34 UTC, Alex wrote:
 As I see this, everything you wrote is correct. :)

 But you compared abstractness with interface usage, initially.
 So... I would say, interfaces are more like the abstract method
 case without any function body. But then, you will have to use
 "override" all across the inherited classes.
Ok. So that means the difference is pretty subtle, give or take a few extra keywords. Which leaves multiple inheritance as the only significant difference? From my perspective it looks like there are two massively overlapping features with some quite arbitrary feeling restrictions and differences. E.g. why can I not inherit from multiple 100% abstract empty classes? Wouldn't that be the same as inheriting from multiple interfaces?
Well, as things stand, _no_ class is 100% abstract, because they all derive from Object, and Object has virtual functions on it with implementations, whereas an interface _is_ 100% abstract. Maybe once we have ProtoObject, it could be argued for allowing 100% abstract classes to be treated as interfaces, but right now, that wouldn't be possible, and even with ProtoObject, it arguably wouldn't be worth the extra complication, since if you really intend to have a 100% abstract class, that's what interfaces are for. There's also the weird complications that come with D's COM support, since that uses interfaces but gets treated differently from how classes are normally treated, and I don't know how that affects the implementation of interfaces. If we'd had ProtoObject from the get-go, I wonder if it would have just been better to implement COM interfaces as being derived from a specific class that's derived from ProtoObject instead of mucking up interfaces the way that we currently hove, but I don't know. I haven't ever actually used D's COM support, so I don't fully understand it. For the most, D's interfaces and abstract classes follow what you get in to have final functions with an implementation). Basically, it seems like what we got with interfaces and abstract classes was copied from Java and then tweaked. I suspect that the separation between interfaces and abstract classes in Java comes from the fact that Object has functions on it, and that same logic carried over to D. In practice, what I would expect to typically happen is that if you're defining a set of functions that a class needs to have but not providing any implementations, then you'd use an interface, whereas if you intend to provide an implementation for any part of it but not all of it, you'd use an abstract class. I wouldn't expect abstract classes with no functions (outside of those from Object) or variables being declared on them to be used much. Maybe someone would have a use case where it would make sense to have a common base class with no implementations, but I can't think of any reason why that would be useful other than preventing classes from being derived from any class from a different class hierarchy, which isn't usually something that's worth preventing. Regardless, the whole weirdness that you're running into with void* is not something that much code would care about, because very little code is going to do something like cast a reference to void*, and the code that does do that is system and expected to deal with it correctly. In general, casting to void* and then casting to anything other than the original type is probably asking for trouble. If I understand correctly, with void*, you're basically doing a reinterpet cast, and that's not usually the type of cast you want when dealing with class references. If I had code where whether casting to an interface or abstract class mattered, I'd want to redesign it so that that didn't matter. - Jonathan M Davis
Aug 11 2019
prev sibling parent Alex <sascha.orlov gmail.com> writes:
On Sunday, 11 August 2019 at 20:32:14 UTC, John Colvin wrote:
 As I see this, everything you wrote is correct. :)

 But you compared abstractness with interface usage, initially. 
 So... I would say, interfaces are more like the abstract 
 method case without any function body. But then, you will have 
 to use "override" all across the inherited classes.
Ok. So that means the difference is pretty subtle, give or take a few extra keywords. Which leaves multiple inheritance as the only significant difference? From my perspective it looks like there are two massively overlapping features with some quite arbitrary feeling restrictions and differences. E.g. why can I not inherit from multiple 100% abstract empty classes? Wouldn't that be the same as inheriting from multiple interfaces?
The overlap is there, but it is not so massive, I would say. If you inherit from abstract classes, then you do not plan to keep them empty. So, the overlap you are speaking about is exactly as large as the amount of "100% abstract empty classes". And for these, the approach to keep the interface as a separate interface seems more convenient, as Adam said. In the end, by forcing an explicit override, some semantic is also implied.
Aug 11 2019
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10.08.19 16:29, John Colvin wrote:
 
 Ok. What would go wrong (in D) if I just replaced every interface with 
 an abstract class?
interface A{} interface B{} class C: A,B{ }
Aug 10 2019
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Saturday, 10 August 2019 at 17:46:37 UTC, Timon Gehr wrote:
 On 10.08.19 16:29, John Colvin wrote:
 
 Ok. What would go wrong (in D) if I just replaced every 
 interface with an abstract class?
interface A{} interface B{} class C: A,B{ }
Yes, I know, I guess it wasn't clear unless you read my previous question, I said "apart from multiple inheritance".
Aug 11 2019
prev sibling parent wjoe <invalid example.com> writes:
On Saturday, 10 August 2019 at 08:20:46 UTC, John Colvin wrote:
 On Friday, 9 August 2019 at 13:39:53 UTC, Simen Kjærås wrote:
 <snip>
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.
The way I look at it: an interface is a guarantee. By saying interface A { void foo()} class B: A {} class C: A {} void bar(A a) {} you guarantee that every class that implements interface A has a function foo and and bar can be called with anything that implements interface A. This is completely unrelated to inheritance. An abstract class is an implementation, most likely a base class. By saying abstract class A {void foo()} class B: A {} class C: B {} void bar(A a) {} you express that you want bar to be called with any subclass of A. e.g. a bitmap button inherits from button which inherits from abstract class widget and implements the signal/slot interfaces. Another pattern is design by introspection. An interface defines what something _is_, by introspection defines what something _CanDo_. That's to say: I don't care if foo is actually (or inherits from) an InputRange as long as it behaves like it.
Aug 14 2019