Thread overview | |||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
October 01, 2002 forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
I quote .... D aims to reduce software development costs by at least 10% by adding in proven productivity enhancing features and by adjusting language features so that common, time-consuming bugs are eliminated from the start. but surely forceing the user to remember to explicitly call a super(...); somewhere in a this(...) that does not call this(.other.params.); is asking for trouble. again my Java and C++ background, where if you don't put in a call to super, then super() is assumed I feel that D should either call super() at the begining of any constructor that does not call super(...) or another this(...) or warn you that you've forgotten something. I have an example class Base { int i; public: this() { i = 10;} void func() { printf( "i:%i\n", i ); } } class Derived : Base { int j; this() { j = 10; } this( int j0 ) { j=j0; } void func() { printf( "i:%i, j:%i\n", i, j ); } } void main( char[][] args ) { Derived d; d = new Derived(); d.func(); d = new Derived(44); d.func(); } which or course outputs i:0, j:10 i:0, j:44 but had 'i' been an object then it would have been null etc etc. (even more frustrated by the effects of simple typo's and omissions) Mike. |
October 01, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Wynn | Mike Wynn wrote:
> I quote ....
>
> D aims to reduce software development costs by at least 10% by adding in
> proven productivity enhancing features and by adjusting language features so
> that common, time-consuming bugs are eliminated from the start.
>
> but surely forceing the user to remember to explicitly call a super(...);
> somewhere in a this(...) that does not call this(.other.params.); is asking
> for trouble.
>
> again my Java and C++ background, where if you don't put in a call to super,
> then super() is assumed
>
> I feel that D should either call super() at the begining of any constructor
> that does not call super(...) or another this(...) or warn you that you've
> forgotten something.
IIRC, this is how things are supposed to work. It may be a compiler bug.
|
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Russell Lewis | "Russell Lewis" <spamhole-2001-07-16@deming-os.org> wrote in message news:3D9A286E.9070900@deming-os.org... > Mike Wynn wrote: > > I quote .... > > > > D aims to reduce software development costs by at least 10% by adding in proven productivity enhancing features and by adjusting language features so > > that common, time-consuming bugs are eliminated from the start. > > > > but surely forceing the user to remember to explicitly call a super(...); > > somewhere in a this(...) that does not call this(.other.params.); is asking > > for trouble. > > > > again my Java and C++ background, where if you don't put in a call to super, > > then super() is assumed > > > > I feel that D should either call super() at the begining of any constructor > > that does not call super(...) or another this(...) or warn you that you've > > forgotten something. > > IIRC, this is how things are supposed to work. It may be a compiler bug. No, super must be called explicitly. If super is not called anywhere, then you get the static initializer, which is what the static default values of each member are. |
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter |
>
> No, super must be called explicitly. If super is not called anywhere, then you get the static initializer, which is what the static default values of each member are.
>
this make life a pain for people writing library classes that are intended for other to use, and still keep them robust.
doesn't this also mean that D will have performance and extra memory usage if you have a class like this
class MyClass
{
// assume MyOtherClass allocates a LOT of resources in all cases
MyOtherClass item = new MyOtherClass(); // force a default in case of
error;
this( char[] a ) { item = new MyOtherClass(a ); }
this( char[] a, char[][] maps ) { item = new MyOtherClass( a, maps ); }
}
because for every instance of MyClass I have to an instance of MyOtherClass
so that my code is robust.
instead of having
this( ) { item = new MyOtherClass(); }
allowing the item member to be initialised only one no matter how the
instance of MyClass is constructed.
which lead me to D allowing both the C++ x = new MyClass; and the Java x =
new MyClass();
does x = new MyClass; call MyClass.this() ?
if not then at least there is a consistancy in that all classes have a
"hidden" implicit constuctor.
but I thought the basic idea behind D was to remove pitfalls and allow
programmers to write the code
in an intuative fashion, adding more "hidden" features IMHO does not do
this.
Mike.
|
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Wynn | 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); } 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. 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. -Walter "Mike Wynn" <mike.wynn@l8night.co.uk> wrote in message news:andk7d$2mtl$1@digitaldaemon.com... > > > > > No, super must be called explicitly. If super is not called anywhere, then > > you get the static initializer, which is what the static default values of > > each member are. > > > this make life a pain for people writing library classes that are intended for other to use, and still keep them robust. > > doesn't this also mean that D will have performance and extra memory usage if you have a class like this > > class MyClass > { > // assume MyOtherClass allocates a LOT of resources in all cases > MyOtherClass item = new MyOtherClass(); // force a default in case of > error; > this( char[] a ) { item = new MyOtherClass(a ); } > this( char[] a, char[][] maps ) { item = new MyOtherClass( a, maps ); } > } > > because for every instance of MyClass I have to an instance of MyOtherClass > so that my code is robust. > instead of having > this( ) { item = new MyOtherClass(); } > allowing the item member to be initialised only one no matter how the > instance of MyClass is constructed. > > which lead me to D allowing both the C++ x = new MyClass; and the Java x = > new MyClass(); > does x = new MyClass; call MyClass.this() ? > if not then at least there is a consistancy in that all classes have a > "hidden" implicit constuctor. > but I thought the basic idea behind D was to remove pitfalls and allow > programmers to write the code > in an intuative fashion, adding more "hidden" features IMHO does not do > this. > > Mike. > > |
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | "Walter" <walter@digitalmars.com> wrote in message news:andhua$2kli$1@digitaldaemon.com... > > "Russell Lewis" <spamhole-2001-07-16@deming-os.org> wrote in message news:3D9A286E.9070900@deming-os.org... > > Mike Wynn wrote: > > > I quote .... > > > > > > D aims to reduce software development costs by at least 10% by adding in > > > proven productivity enhancing features and by adjusting language > features so > > > that common, time-consuming bugs are eliminated from the start. > > > > > > but surely forceing the user to remember to explicitly call a > super(...); > > > somewhere in a this(...) that does not call this(.other.params.); is > asking > > > for trouble. > > > > > > again my Java and C++ background, where if you don't put in a call to > super, > > > then super() is assumed > > > > > > I feel that D should either call super() at the begining of any > constructor > > > that does not call super(...) or another this(...) or warn you that > you've > > > forgotten something. > > > > IIRC, this is how things are supposed to work. It may be a compiler bug. > > No, super must be called explicitly. If super is not called anywhere, then you get the static initializer, which is what the static default values of each member are. Hmm. Constructor is supposed to initialize the object. The existence of the constructor itself guarantees that an object cannot be created withouth initialization. Actually, with good-designed methods, it guarantees internal consistency of the object. If it is *optional* to call the constructor, then you should not call that method "constructor". It does not fit into the definition. What kind of design needs to avoid the constructor? Example? This ruins one of the important concepts of OOP. Please require calling the super constructor! Yours, Sandor |
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | I think that keeping the focus on unit testing and design-by-contract is more important that tradition here. Even though it seems to me to violate the principle of least surprise (in terms of someone coming in from C++), I think that it's easy enough to keep in mind, and getting caught by a bug in this once will likely push new people more strongly towards using invariants, unittests and the like is a more effective manner.
Evan
Walter wrote:
> 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);
> }
>
> 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.
>
> 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.
>
> -Walter
|
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | "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, and to an inexperianced (in D) programmer they may think that it is the library at fault not their code. > 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 ? 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. > 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. 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()" 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 ); } } } why is `static this() { ... }` always called for a module ? when `this() { ... }` has to be manually called for a base class. |
October 02, 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:anege2$ijt$1@digitaldaemon.com... > I think that keeping the focus on unit testing and design-by-contract is more important that tradition here. Even though it seems to me to violate the principle of least surprise (in terms of someone coming in from C++), I think that it's easy enough to keep in mind, and getting caught by a bug in this once will likely push new people more strongly towards using invariants, unittests and the like is a more effective manner. > Tradition is not always wrong, and why change it if it works ? I'm not sure its so much "least surprise" but more implied by syntax in Delphi/Object pascal, where constructors can have any name (and can be virtual) to call the super constructor you use `inherited Create;` (in the constuctor or to allow a base constructor to be inherited by a abstract class.) the syntax is different enought that it implicity warns the unwary that what they know to be true might not be, the same is true with Perl. Php also has classes and again as it looks like a Perl+Java hybrid so its easy to not be expect any predefined behaviour. D however looks like C++ and Java both of which have predefined behaviours related to constructors. I know the vtables are different in constructors in C++ and Java, but I always found the C++ method annoying, (look at Borlands TurboVision Sources which uses a virtual base class to allow a base class to call a derived class method at construction time). also Delph/Object Pascal have a more verbose syntax, no +=,++ etc so it is more natural to expect to have to type more code. C/C++/Java have a lot of nice short cuts, break, continue, return etc as keywords not function calls so it seems logical that any language that inherits their syntax will also inherit their default behaviours. I never got to grips with Smalltalk/Self syntax so have no idea what they do not that "becuase they do it" is any argument for doing something, but the reason why they do is. I belive that the question should be what method is the most robust, most intuative and least likely to cause problems. I personally think that it should be this( params ) : this/super( possible static method calls with params, or just params or consts) { body } within any code obj.renew( params ); or within a constructor this() can be called to "reconstruct" and object into a new state. and also have Delphi sytle 'virtual' constructors so a type of 'class' can be passed to methods as in. Object makeANew( char[] name, class objClass ) { return objClass.new( name ); } MyObj makeANew( int len, class<MyObj> objClass ) { return objClass.new( len ); } or MyObj makeANew( int len, classref(MyObj) objClass ) { return objClass.new( len ); } I feel that class invariants should be evaluated when the code leaves that classes methods but not if you are calling super.whatever(); because that is the "contract". To be realy safe all super.invariants should be called too to make sure this class have not disturbed the super classes state before entering/reentering subclass or external code. I do not see how not calling a default constructor will make those coming from a C++/Java background use invariants/unittest, (especially unittest) it's not my Base class that will have the bugs its the derived classes and I have no way to "force" default behaviour, the best I can achive is checks at runtime that they(the writers of the derived classes) wrote the right think. I want to make these checks at compile time becuase as we all know runtime checks are great, but some conditions only occur under special conditions, it is much better to be able to catch potential problems at compile time. than lots of testing, unit test are a great help, but as I've kept saying the problem is that the derived classes need the unittests not the base class. I worked on a large (ish) perl project and had quite a lot of trouble getting other to 'use strict' because it was not until they had actually run fowl of a few problems could they understand why making sure variables where at least declared first was a good idea. this is a very similar situation. 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. 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. 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. Mike. |
October 02, 2002 Re: forgetting to call super doesn't call any super constructors. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Wynn | In article <anej18$lmi$1@digitaldaemon.com>, Mike Wynn says... >"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, and to an inexperianced (in D) programmer they may think that it is the library at fault not their code. First, let me state that my personal feeling is that a derived class's this() should be required to call super(), causing a compiler error if it is not present. If a real need exists for derived classes that must not (or should not, if the "should not" is strong enough to warrant a language mod) call their base class constructor, then I think that special case should warrant a special purpose keyword or other tagging construct. Perhaps "uninitialized" or something similar, to flag that this derived class will not being calling the superclass constructor. Having said all of that, if we are stuck with the invariant approach, one way to ensure that the invariant catches what you want and is somewhat clear as well is to do something like this (pardon any syntax errors in this -- I haven't done any real programming in D, so my reflexes aren't there yet...): class MyBaseClass { public: // whatever this() {MyBaseClass_has_been_constructed = true; /* other stuff */} protected: // whatever private: // whatever bit MyBaseClass_has_been_constructed = false; } invariant { assert(MyBaseClass_has_been_constructed); } Because MyBaseClass_has_been_constructed is a private member, the only way for derived classes to change its value is by calling super(). A better name is probably possible for the variable -- telling someone that the base class was not constructed doesn't explain how it failed to be constructed. If the assertion can only ever fail due to a derived class forgetting super(), the name might be better as MyBaseClass_has_been_built_with_call_to_super, or some similarly painful thing... This kind of programming, while still a runtime check, is not dependent on situations or conditions. The assertion will fail unless super() was called. It does, however, require all base class developers to be conscientious and write this kind of invariant. More painfully, it requires all class developers to be this conscientious, because even though they may not have expected anyone to derive a class from their class, someone may find a need to do so. 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). 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. I think the earlier call should be made so that the derived class constructor is guaranteed that the base constructor has satisfied itself and that the object is in a valid state (at least for the base class) before the remaining derived code tries to use any of it. After all, you call super() because you want yourself to be set up like an instance of the base class, so you are probably going to assume that that has been done. A guarantee about that should be made. As an optimization step, of course, if super() is the _last_ thing in the derived constructor, then the "end of derived constructor multiple invariant check" would suffice -- the base constructor would not need to be called twice in a row (end of super() and end of this()). Mac |
Copyright © 1999-2021 by the D Language Foundation