www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Recommended ways to handle final classes

reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
parent reply "JS" <js.mdnq gmail.com> writes:
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,

     -- Joe
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., 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
parent reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
next sibling parent reply "Dicebot" <public dicebot.lv> writes:
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
parent reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
parent reply "Dicebot" <public dicebot.lv> writes:
On Friday, 16 August 2013 at 16:27:37 UTC, Joseph Rushton 
Wakeling wrote:
 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
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 ;)
Aug 16 2013
parent reply "Joseph Rushton Wakeling" <joseph.wakeling webdrake.net> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
[...]
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.
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.)
You need to be aware though that I am personally an OOP hater and
my advices should be reconsidered in with that in mind ;)
[...] 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 Information
Aug 16 2013
parent "Dicebot" <public dicebot.lv> writes:
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
prev sibling parent reply "JS" <js.mdnq gmail.com> writes:
On Friday, 16 August 2013 at 15:47:21 UTC, Joseph Rushton 
Wakeling wrote:
 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?
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).
Aug 16 2013
parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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