October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Wynn | "Mike Wynn" <mike.wynn@l8night.co.uk> wrote in message news:aner8q$utg$1@digitaldaemon.com... > Tradition is not always wrong, and why change it if it works ? I don't agree that the way C++ constructors works does work. I've had so many bugs from having multiple constructors for an object, adding a field to the class, and forgetting to initialize it in one of the constructors. I've wanted many times to have more complex constructors call a more basic constructor for the same class (not just the super class). I've wanted to do some complex computation before calling the base class constructor. All these are broken in C++. D offers great flexibility in building constructors, as well as offering guarantees that every field gets initialized to something predictable. D offers contracts to verify more complex relationships that must be held. > I confess I'm not the worlds greatest programmer, I make all manner of stupid mistakes, I cut'n'paste classes and forget to change things, I forget > to do all manner of things when writing code that I should, and I like languages that warn me that I'm being an idiot when if forget to do the basics. especially as I have used several with similar syntax and am for every getting operator precidence wrong or trying to use language features in the wrong language how I wish C have Perl's redo. While D offers better compile time checking than C++, especially for the case of inadvertantly not initializing a class member, you can get huge gains in robustness with even a modest use of class invariants, contracts, and unit tests. I was surprised at how many of my stupid bugs unit tests have caught in even the simple runtime library code. > Who is D aimed at and why should I use it in perference to C/C++/Java/Delphi/Kylix/Perl/Php/Lua and what does it offer that would stop > me from learning C#/Python/Tcl/Modula2,3/Oberon/VB > I was under the impression that D would offer me most of C/C++/Java/Delphi > without having to remember > 3 different syntaxes and better compile time error checking, more robust > code, performance matched feature for feature (so I should not be able to > gain a huge speed up by changing away from D) > all with less pitfalls for the unwary. > allowing me to write better code, quicker that was less prone to simple > programmer errors. I believe it does. > this behaviour combined with the new interface symantics has started me thinking that I should upgrade my VB6 to VS .NET and see what C# offers. or > investigate the gcc Java complier. The interface semantics are going to get redone <g>. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | "Mac Reiter" <Mac_member@pathlink.com> ha scritto nel messaggio news:anf1ji$15s4$1@digitaldaemon.com... [...] > To me, this is highly reminiscent of the problem with the "virtual" keyword in > C++. The designers chose to not do virtual by default because of the performance and memory penalties, but the result is that almost all C++ programmers make extremely difficult to locate bugs because they forget to specify virtual on a member function. Similarly, the major reason I can see for > not wanting to call super() is when you know (or think you know) that you are > going to overwrite all of its variables, so "why waste time letting it set them > up?". The problem with this thinking is: > > 1. You don't know about the private members it may be setting up, and you can't > do anything about them even if you did. > 2. If a problem is discovered in the base class, and the fix changes either the > number of members or the constructor, your derived class will not take advantage > of the fix because you won't be aware of the new members and you aren't using > the constructor. In theory, the base invariant should be modified to catch this > sort of thing, but the reality is that it won't be able to catch everything. > And, as mentioned by others, even if it catches it, the explanation for what is > wrong won't be terribly clear. > > Like I said earlier, my preference is strongly in favor of requiring a call to > super() in any derived class this(), with the addition of some kind of > "uninitialized" keyword/tag if such functionality is deemed necessary (either > for correctness or performance, but I really only think that correctness is a > valid argument here). This is a VERY GOOD point. I completely agree with you, Mac. Ciao |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Wynn | "Mike Wynn" <mike.wynn@l8night.co.uk> wrote in message news:anej18$lmi$1@digitaldaemon.com... > "Walter" <walter@digitalmars.com> wrote in message news:andpff$2s31$1@digitaldaemon.com... > > The static initializer must be evaluated at compile time, so it can't be > > MyOtherClass item = new MyOtherClass(); // force a default in case of > > It should be null. To make a robust class, try a class invariant: > > invariant > > { > > assert(item != null); > > } > yes that will catch the error at runtime, but not tell the user of the library (my consern here is classes that form part of a library) what they did wrong, Correct, but you can always add: if (item == null) printf("item is not set at close of constructor - perhaps you need to call super()?\n"); before the assert. > and to an inexperianced (in D) programmer they may think that it is the library at fault not their code. It may take a while for new D programmers to get used to the idea of getting contract exceptions rather than the crashes one gets from using C++ api's wrong <g>. > > The base invariant will get called at the close of the derived > constructor, > > and will so guarantee that the derived constructor did its work as the > base > > class designer intended. > why is base invariant not called at the close of the base constructor ? > that would be where I would expect it to be, does this mean that it is not > called if the class is not > subclassed ? is it just after the immediate sub class constructor, or after > the outermost ? Oh, you're right, I made a mistake. It *is* called at the close of the base constructor, as well as at the close of the derived constructor. > given that I can call this() and super() repeated times within any given constructor and have code between them surely the Base state should be 'correct' at all times when the 'thread' is not running Base code. You're right. > > C++ has no static initialization of class members, so if you do not explicitly initialize them in the constructor (and there is no check for this), they are initialized to garbage. I've had endless bugs from that. > D's > > static initialization of all members before the constructor is called eliminates those errors. > I agree, Java solves this by initialising all members to 'null' before > calling the constructor > I personally would rather this behaviour, and this() gettting called to the > current scheme. I'm not sure that Java's semantics here offer anything over D's. > it seems to me that you have just changed all the problem with members not > getting initialised from > the base class into the derived class and created a new set of possible > programmer errors. > > so the base class designer have to remember that there are > two this()'s one implicit and one explicit, one that allow code to be run, > one that does not > > consider: > > class Base { > MemberItem item; > this() { item = new MemberItem(); setParent.item( this ); } > } > > how do I make sure MemberItem::setParent is called and item is set? > I assume `auto MemberItem item;` would make item's static constructor to be > called not "MemberItem::this()" Yes. > class Base { > MemberItem item; > bit initialised; > this() { item = new MemberItem(); initialised = true; setParent.item( > this ); } > invariant { > if ( !initialised ) { item = new MemberItem(); initialised = true; > setParent.item( this ); } > } > } No, invariant should not be used to set any state. It should only be used to verify that the class invariants hold - this is because invariants can be removed with a compile time switch. You also don't need an 'initialised' flag, just test 'item' for null. > why is `static this() { ... }` always called for a module ? when `this() { > ... }` has to be manually called > for a base class. this() does not need to be manually called. It is implicitly called for any: new Base; It's the super() that needs to be manually called. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | "Mac Reiter" <Mac_member@pathlink.com> wrote in message news:anf1ji$15s4$1@digitaldaemon.com... > I also agree with someone else's comment that the base invariant should be called at the end of the base constructor, even when invoked through super(). I > also think it should be called _again_ at the end of the derived constructor, > since it is a valid invariant for the derived class. That should be the way it works now, I miswrote it. Some issues with the compiler flagging not calling super: class A { this() { .. do general construction .. } this(int x) { this(); .. do more construction ..} } Calling super in each constructor means it gets called twice. Secondly, there is: this() { if (...) super(); } So super() isn't always run. It's impossible for the compiler to verify that super() is called exactly once in the general case. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sandor Hojtsy | "Sandor Hojtsy" <hojtsy@index.hu> wrote in message news:aneat4$d56$1@digitaldaemon.com... > What kind of design needs to avoid the constructor? Example? See my reply to Mac Reiter. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | Walter wrote:
> No, invariant should not be used to set any state. It should only be used to
> verify that the class invariants hold - this is because invariants can be
> removed with a compile time switch. You also don't need an 'initialised'
> flag, just test 'item' for null.
Would it be possible (or even useful) to have the compiler throw a warning or error if someone does something with side effects in a invariant, unittest, or contract statement? I realize that the checking would be non-trivial, but it might be helpful to transitioning programmers in terms of laerning how to use them correctly. I could see, for example, someone using in{} pre-conditions to do something like flattening their input into a certain range, and then having weird bugs when they compile for release and then it isn't flattend into that range anymore. Perhaps it's a non-issue, but it's an idea, at the least.
Evan
|
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Evan McClanahan | "Evan McClanahan" <evan@dontSPAMaltarinteractive.com> wrote in message news:anjk6f$308i$1@digitaldaemon.com... > Would it be possible (or even useful) to have the compiler throw a > warning or error if someone does something with side effects in a > invariant, unittest, or contract statement? I realize that the checking > would be non-trivial, but it might be helpful to transitioning > programmers in terms of laerning how to use them correctly. I could > see, for example, someone using in{} pre-conditions to do something like > flattening their input into a certain range, and then having weird > bugs when they compile for release and then it isn't flattend into that > range anymore. Perhaps it's a non-issue, but it's an idea, at the least. That's an important issue. The problem is, I think it is impossible for the compiler to verify that the contracts have no side effects. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | I forgot to mention that Java requires any this() or super() call to be the first statement in a constructor. Things like: this() { a = b; super(); } are not allowed. Java's syntax is better than C++'s, but it still does not allow any non-trivial computation before the super constructor gets called. D will allow things like: this() { .. complex calculation .. if (...) super(); else super(abc); } |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | > > Calling super in each constructor means it gets called twice. Secondly, there is: > > this() > { > if (...) > super(); > } > > So super() isn't always run. It's impossible for the compiler to verify that > super() is called exactly once in the general case. > yes you can ! by walking through the syntax tree and checking that there is a super() on all possible paths just like javac can detect if you have not assigned a value to a local before use (does err in faviour of safetly) Mike. |
October 04, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | > > No, invariant should not be used to set any state. It should only be used to > verify that the class invariants hold - this is because invariants can be removed with a compile time switch. You also don't need an 'initialised' flag, just test 'item' for null. > but what if later on the item CAN be null, just not when initialised (horrid case I agree, and I'm being devil advocate here). > > why is `static this() { ... }` always called for a module ? when `this() { > > ... }` has to be manually called > > for a base class. > this() does not need to be manually called. It is implicitly called for any: > new Base; > It's the super() that needs to be manually called. this I find inconsistant, why `new Base();` and `new Base;` the former I prefer, the latter if you have it should not call this(); I had a good chat yesterday with a C++ programmer friend on mine, and we discussed the constructor problem, and why Java has the restrictions that it does; Java much have a call to super as the first source instruction, but the VM allows bytecode before the invoke_special call to the super( blablabla ); as long as the 'this' pointer is not referenced (checked by the verifier). so a constructor can be : class MyClass { MyClass( int a, int b ) { super( MyStaticFunc( a ), b ); ..... } String MyStaticFunc( int a ) { return new Integer( a ).toString(); } } BUT you cannot catch an exception from the base class because that would allow an incomplete Object to be created. the solution (not perfect but influenced but this and Delphi/Perl which allow a constructor to have any name) was this (mix of code and BNF, regexp); class MyClass { this( params ) : [super( static_call(params) | params )], [member(static_call(params) | params )] { .... code with try catch allowed, any members not set in the initialiser list ... will be null 0, 0.0, 0.0+i0.0 etc. } // 'creator' <name>'(' <param list> ' )' <block> creator restore( params ) { // on entry this will be null if called direct or be non null if called from a sub class this( params ) ... code .... .. many calls to this as you like try { // will create a clone and then call this on the new object, if good will update this pointer // items that are not explicitly modified by the constructor will not be effected and will // be unchanged. this( stuff ); super( params ); // will call super.restore( params ); // again this will create a clone first so that on exception the state is consistent. } catch ( Exception e ) { // this will be the this BEFORE the try block NOT the partial made object. } super( params ); // calls super.restore(); // no need to clone (not in a try clause) } } syntax for using MyClass obj = MyClass.restore( params ); // obj MAY BE NULL a bit like Delphi. a creator may apear just a static function that calls new with an implicit this, but the return type is that of the class and it supports inherited calls (a class virtual if you like). I'm not sure by you may consider the following MyClass obj = MyClass.restore( params ); obj.restore( other_params ); // re make the object; again if this is wrapped in a try clause the compiler will have to "clone" it first to retain a stable state. and the very delphi class MyDerivedClass : MyClass { ...defines a creator restore ... } MyClass func( class MyClass cptr ) { return cptr.restore( params ); } MyClass obj = func( MyDerivedClass ); obj will be of type MyDerivedClass, and will have be created via the MyDerivedClass.restore. this allows both the quick obj = new Class(); for simple objects, and the creator methods for more complex construction. IMHO: a constructor should make the object and initialise it, but not do a huge amount of work and above all should always give you an object that is in a 'stable' state. Mike. |
Copyright © 1999-2021 by the D Language Foundation