digitalmars.D.learn - Recommended ways to handle final classes
- Joseph Rushton Wakeling (17/17) Aug 16 2013 Hi all,
- JS (10/31) Aug 16 2013 Can you describe how this helps?
- Joseph Rushton Wakeling (4/8) Aug 16 2013 The point is, you should be able to use _A for inheritance, A for perfor...
- Dicebot (8/8) Aug 16 2013 It very is likely that you in fact want A to be a struct and _A -
- Joseph Rushton Wakeling (5/7) Aug 16 2013 The idea is maybe more to be able to have,
- Dicebot (13/21) Aug 16 2013 That makes some sense indeed, all you need is to stop naming base
- Joseph Rushton Wakeling (7/18) Aug 16 2013 I'm happy to consider alternative ideas. I find that mixins are
- H. S. Teoh (39/52) Aug 16 2013 This probably doesn't relate directly to your problem at hand, but armed
- Dicebot (12/20) Aug 16 2013 I was not really speaking about OOP concept as-is but about
- JS (58/68) Aug 16 2013 You can't get them both at the same time though, which you might
- Joseph Rushton Wakeling (36/48) Aug 29 2013 Sorry for delayed response here, I've been fairly heavily focused on oth...
Hi all, I'm writing some class-based code and so my concerns have turned to the issue of final classes vs. inheritance. Suppose that you want a particular class to be able to act as a base class, but also to perform at top speed. Is the following kind of pattern worth considering: class _A { // ... implementation, with virtual functions } final class A : _A { // ... don't add anything! } ...? Or are there other recommended ways to handle how to get speed while still allowing the option of inheritance? Thanks & best wishes, -- Joe
Aug 16 2013
On Friday, 16 August 2013 at 15:12:03 UTC, Joseph Rushton Wakeling wrote:Hi all, I'm writing some class-based code and so my concerns have turned to the issue of final classes vs. inheritance. Suppose that you want a particular class to be able to act as a base class, but also to perform at top speed. Is the following kind of pattern worth considering: class _A { // ... implementation, with virtual functions } final class A : _A { // ... don't add anything! } ...? Or are there other recommended ways to handle how to get speed while still allowing the option of inheritance? Thanks & best wishes, -- JoeCan you describe how this helps? Sure, if you use A the compiler should be able to optimize method calls but you can't use A in inheritance because it is a final class. e.g., A a = new _A; // wrong _A a = new A; // right, but a will use virtual functions. The final class only prevents class B : A AFAIK but the vtable is still intact.
Aug 16 2013
On 08/16/2013 05:40 PM, JS wrote:Can you describe how this helps? Sure, if you use A the compiler should be able to optimize method calls but you can't use A in inheritance because it is a final class. e.g.,The point is, you should be able to use _A for inheritance, A for performance, and they both have the same functionality. Or am I mistaken?
Aug 16 2013
It very is likely that you in fact want A to be a struct and _A - template mixin. In other words, you are trying to express a relationship that does not really model anything in OOP terms. You proposal will work but it will essentially be just hacky implementation of mixins with some extra informational overhead. Maybe we can come with a better proposal if you explain what object model are you trying to achieve?
Aug 16 2013
On 08/16/2013 06:12 PM, Dicebot wrote:Maybe we can come with a better proposal if you explain what object model are you trying to achieve?The idea is maybe more to be able to have, _A -- base class A : _A -- final class that doesn't change _A's functionality B : _A -- final class that _does_ override _A's functions
Aug 16 2013
On Friday, 16 August 2013 at 16:27:37 UTC, Joseph Rushton Wakeling wrote:On 08/16/2013 06:12 PM, Dicebot wrote:That makes some sense indeed, all you need is to stop naming base class "_A" and give it a proper meaningful name that reflects its role in object model :) From the conceptual purity point of view I still think that having bunch of template mixins instead of _A and just combining them into A or B is the proper way (inheritance should not be used as code reusage tool) - but D may not have powerful enough tools to makes this approach convenient, so resorting to old-school inheritance sounds pragmatical. You need to be aware though that I am personally an OOP hater and my advices should be reconsidered in with that in mind ;)Maybe we can come with a better proposal if you explain what object model are you trying to achieve?The idea is maybe more to be able to have, _A -- base class A : _A -- final class that doesn't change _A's functionality B : _A -- final class that _does_ override _A's functions
Aug 16 2013
On Friday, 16 August 2013 at 17:08:36 UTC, Dicebot wrote:That makes some sense indeed, all you need is to stop naming base class "_A" and give it a proper meaningful name that reflects its role in object model :)Hey, it's shorthand :-)From the conceptual purity point of view I still think that having bunch of template mixins instead of _A and just combining them into A or B is the proper way (inheritance should not be used as code reusage tool) - but D may not have powerful enough tools to makes this approach convenient, so resorting to old-school inheritance sounds pragmatical.I'm happy to consider alternative ideas. I find that mixins are very useful like this but also find that stuff tends to gain complexity quite rapidly with their use.You need to be aware though that I am personally an OOP hater and my advices should be reconsidered in with that in mind ;);-) Thanks for the thoughts and advice!
Aug 16 2013
On Fri, Aug 16, 2013 at 07:51:22PM +0200, Joseph Rushton Wakeling wrote:On Friday, 16 August 2013 at 17:08:36 UTC, Dicebot wrote:[...]This probably doesn't relate directly to your problem at hand, but armed with alias this, D structs can be made to behave as if they had inheritance (though not fully): struct Base { int x; void baseMethod() {} } struct Derived { Base __base; alias __base this; int y; void derivedMethod() {} } Derived d; d.x = 1; // Can access "base struct" members directly d.baseMethod(); d.y = 2; // Obviously can also access "derived" members d.derivedMethod(); You can actually implement your own version of OO by careful use of this trick (though it would require manual maintenance and wouldn't have language-level support). Of course, this isn't necessarily restricted to structs alone. You can also do this with structs that you 'alias this' into classes, to make it appear as though the struct methods are among the class methods. (Unfortunately multiple alias this isn't implemented yet, so this is somewhat limited. But you should be able to pull off some pretty clever tricks with it.)From the conceptual purity point of view I still think that having bunch of template mixins instead of _A and just combining them into A or B is the proper way (inheritance should not be used as code reusage tool) - but D may not have powerful enough tools to makes this approach convenient, so resorting to old-school inheritance sounds pragmatical.I'm happy to consider alternative ideas. I find that mixins are very useful like this but also find that stuff tends to gain complexity quite rapidly with their use.[...] I'm not an OOP *hater* per se, but I *am* a skeptic about applying OOP anywhere and everywhere, even when it doesn't really belong (*cough*Java*cough*). Different problems need different solutions; insisting on turning a screw with a hammer only leads to poor hacks, boilerplate, and other code smells. *ahem* http://www.ioccc.org/2005/chia/chia.c *cough* ;-) T -- "I speak better English than this villain Bush" -- Mohammed Saeed al-Sahaf, Iraqi Minister of InformationYou need to be aware though that I am personally an OOP hater and my advices should be reconsidered in with that in mind ;)
Aug 16 2013
On Friday, 16 August 2013 at 18:20:46 UTC, H. S. Teoh wrote:I'm not an OOP *hater* per se, but I *am* a skeptic about applying OOP anywhere and everywhere, even when it doesn't really belong (*cough*Java*cough*). Different problems need different solutions; insisting on turning a screw with a hammer only leads to poor hacks, boilerplate, and other code smells.I was not really speaking about OOP concept as-is but about traditional implementations in languages like C++ or Java that suggest using class hierarchies for ~everything. Really loved this article found on reddit few months ago, it pretty much sums up my feelings on topic: https://lwn.net/Articles/548560/ . It speaks about Go and Rust but key point is generic - classical class-based OOP only makes sense if you need a run-time polymorphic behavior (and even than there are exceptions). I believe that with the development of meta-programming tools completely new approach will emerge eventually.
Aug 16 2013
On Friday, 16 August 2013 at 15:47:21 UTC, Joseph Rushton Wakeling wrote:On 08/16/2013 05:40 PM, JS wrote:You can't get them both at the same time though, which you might just make _A final. The whole point of inheritance is to treat derived classes as if they were base classes. e.g., code was designed to use class _A, you come along, extend _A to A, but "trick" to the code to use your A(even though it thinks it's an _A). Now, when you use your final class A where _A's are suppose to be, there can be no optimization made by the compiler(unless it can determine the object really is an A). e.g., class _A { void foo() { writeln(); } } final class A : _A { } ... _A a = new A; ... a.foo(); // can't be inlined because a's type is not CT final. A aa = new A; ... aa.foo(); // can be inlined because aa's type is final(no need for vtable) The only difference is that a is _A and aa is A. So when you use your final class with inheritance, it becomes useless because it gets treated as the base class, which is not final. When one does _A a = new A; a is of type _A regardless of what we actually stick in a as far as what the compiler see's(unless it's real smart but then, final is not needed). A final class is simply to stop someone from deriving from it. The compiler can make optimizations based on this info IF it knows that the object is from a final type. Here is a more real world example class Shape { } final class fShape : Shape { } class Rectangle : Shape { } final class fRectangle : Rectangle { } Shape s = new fRectangle; // useless, just use Rectangle, the compiler will probably never know s is an fRectangle. (it might in some cases but probably not worth it) That is the oop way, this is not: fShape s = new fRectangle; // invalid, fRectangle is not derived from fShape. // compiler knows s is a final class and can make optimizations on it. Because we can't assign concrete shapes to fShape(because they have to derive from them, but can't because the class is final), it becomes useless to use(no way to do oop). To do oop you have to have derivation from base classes, but final stops that. Hence your two goals(being able to use oop and being able not to(using final) are mutually exclusive). The only way it can be useful is if the compiler can determine if an object is final at compile time... but if it can do this then it doesn't need to be final in the first place(it will still be able to inline methods).Can you describe how this helps? Sure, if you use A the compiler should be able to optimize method calls but you can't use A in inheritance because it is a final class. e.g.,The point is, you should be able to use _A for inheritance, A for performance, and they both have the same functionality. Or am I mistaken?
Aug 16 2013
On 16/08/13 23:21, JS wrote:You can't get them both at the same time though, which you might just make _A final. The whole point of inheritance is to treat derived classes as if they were base classes.Sorry for delayed response here, I've been fairly heavily focused on other things. :-( I think it's best if I describe explicitly the code situation I have. I wasn't trying to be coy before, but just assumed that the pattern I described would either be completely typical or not at all useful -- so I thought a simple example would be better. (I was also away from the computer when writing several replies, short examples are nicer to write on a phone:-) The _real_ code is my graph library. The basic graph type is currently implemented as a final class: https://github.com/WebDrake/Dgraph/blob/master/dgraph/graph.d#L30-L343 However, this has some speed issues, so I have a fork where that class is extended in several ways: https://github.com/WebDrake/Dgraph/blob/cache/dgraph/graph.d#L30-L504 The extension is fairly simple. The class gains a few extra internal data types which cache various calculated values; a couple of functions gain some extra lines to appropriately take care of that extra data; and a couple of other functions are substantially rewritten, to take advantage of that extra data being available. Now, I'd assumed that this would be a cause where making the one a derivative of the other would make sense, but after discussion here, I'm not so sure that's appropriate (as per Dicebot's remark, "inheritance should not be used as code reusage tool").e.g., code was designed to use class _A, you come along, extend _A to A, but "trick" to the code to use your A(even though it thinks it's an _A).That's _not_ the case here, there's no intention of trying to have runtime generics through base interfaces. I'm trying to do something closer to how RNGs are implemented in Phobos, with many different implementations sharing a common public API.To do oop you have to have derivation from base classes, but final stops that. Hence your two goals(being able to use oop and being able not to(using final) are mutually exclusive).I'm not trying to "do OOP" per se, in the sense that I'm not trying to create a hierarchy of more general to more specific along the lines of Shape => Rectangle => Square, etc. But in this case inheritance seems to make sense. The choice of classes in the first place wasn't because of their inheritance properties, but because the nature of the internal data means the Graph is pretty much unavoidably a reference type, and it's the simplest way to enforce that.The only way it can be useful is if the compiler can determine if an object is final at compile time... but if it can do this then it doesn't need to be final in the first place(it will still be able to inline methods).Oddly enough, when doing a couple of speed tests with and without the "final" in place, sometimes LDC seemed to be able to optimize better without it there. Though it might be a fluke.
Aug 29 2013