Must I close TStreams ( Reading )

BlitzMax Forums/BlitzMax Programming/Must I close TStreams ( Reading )

Gabriel(Posted 2007) [#1]
The Delete() Method of TIO ( which is what TStream is derived from ) calls the Close() method. So I'm wondering if I really need to do it myself or not. Obviously for writing, it's important because I can't guarantee what's written. But for reading, it doesn't seem like it really matters. Am I wrong?


Scott Shaver(Posted 2007) [#2]
close them, you might be able to get away with not doing it but it is extreemly bad coding practice


Gabriel(Posted 2007) [#3]
Yes, I suppose you're right. I could have really made a bunch of code a lot shorter and neater without closing the streams, but I guess it's not a good habit to get into.

Thanks.


H&K(Posted 2007) [#4]
Well...... unless they are being closed when the stream is no longer refference.


marksibly(Posted 2007) [#5]
Hi,

They will close themselves 'eventually' - but 'eventually' is kind of unpredictable.

If something else wants to later write to the file and 'eventually' hasn't happened yet, you'll be in trouble.

So it's best to close files as soon as you're done with them.


Fabian.(Posted 2007) [#6]
I think the delete method in type TIO doesn't make sense at all, because it just calls the close method of TIO, which is empty. If There's something derivered from type TIO its close method doesn't get called due to TIO's delete method. Here's an example:
Strict
Framework brl.blitz

F
GCCollect

Function F ( )
  Global Stream:TStream = New TStream
  Stream = Null
EndFunction

Type TIO
  Method Close ( )
    WriteStdout "TIO's Close method.~n"
  EndMethod

  Method Delete ( )
    Close
  EndMethod
EndType

Type TStream Extends TIO
  Method Close ( )
    WriteStdout "TStream's Close method.~n"
  EndMethod
EndType
TStream's Close method doesn't get called at all except if you're explicitly calling it. The implementation of the Delete method in type TIO doesn't make sense - it just calls the empty Close method of type TIO (and it confuses every person reading the source).


FlameDuck(Posted 2007) [#7]
I think the delete method in type TIO doesn't make sense at all, because it just calls the close method of TIO, which is empty.
No it doesn't. It calls the close method of whatever subclass instance of TIO is being garbage collected.


Grey Alien(Posted 2007) [#8]
Now I'm confused after running the example code it does appear to call TIO's close() not the extended type's close() which is what I thought it would do due to polymorphism...


TomToad(Posted 2007) [#9]
I believe what is happening is that BlitzMAX is deleting the types recursively starting with the bottommost child type. So by the time TIO is called, TStream is already deleted leaving only the base type's Close() to call.
Type TBase
	Method Close()
		WriteStdout("TBase Close()~n")
	End Method
	
	Method Tryit()
		WriteStdout("TBase TryIt()~n")
	End Method
	
	Method Delete()
		Close
	End Method
End Type

Type TChild Extends TBase
	Method Close()
		WriteStdout("TChild Close()~n")
	End Method
	
	Method Tryit()
		WriteStdout("TChild Tryit()~n")
	End Method
	
	Method Delete()
		Close
	End Method
End Type

F
GCCollect()

Function F()
	Global MyChild:TChild = New TChild
	MyChild.Tryit()
	MyChild = Null
End Function



Fabian.(Posted 2007) [#10]
TomToad explained it perfectly; this is the way how BlitzMax handles this.
So the Delete method in type TIO does nothing more than calling the Close method in type TIO, however this Close method is empty. This is just confusing - Mark should remove the implementation of the Delete method in the next update of the stream module.


Grey Alien(Posted 2007) [#11]
Aha I see, thanks for the explanation TomToad.


FlameDuck(Posted 2007) [#12]
I believe what is happening is that BlitzMAX is deleting the types recursively starting with the bottommost child type.
Sure looks like it. Sounds a bit wierd to me. I'm not entirely certain I'm a big fan of that kind of behavior.

Mark should remove the implementation of the Delete method in the next update of the stream module.
Or alternatively he should fix it so Delete is only invoked for the proper type, not it's super classes, unless explicitly required.


Fabian.(Posted 2007) [#13]
Or alternatively he should fix it so Delete is only invoked for the proper type, not it's super classes, unless explicitly required.
I'm sure even brl has written some code based on this behaviour, so this change would cause more problems than it fixes.


QuietBloke(Posted 2007) [#14]
Isnt delete doing the reverse of New() and is therefore required. I would kind of expect it to happen like that.
Or have I read the posts wrong ?

For example if TBase allocated some memory for whatever reason in the New() method.

So.. when a TChild is created it will call New() for the TBase first and then New() for the Child().

When TChild is deleted surely then you want to make sure the the Delete() for TCHild is called and then the Delete() for TBase is called to free up the memory. ?

What you surely dont want is to ever allow a derived class to override Delete() for the TBase class and thus leave a memory leak ?.


Dreamora(Posted 2007) [#15]
Yupp. Delete is the "inverse operation" of new()
Would definitely be a bad idea to prevent base class destructor / constructor beeing called recursively.

But in BM its all a little "getting used to" when it comes to "polymorphism"

Reason is that constructor / destructor are called recursively.
Functions / Methods are logically not.

Thats how all / all used languages behave.

The point where BM breaks is fields: If you create a field with the same name in a derived class, it will actually create another instance of that field, it won't override the type of the original instance or something similar (BM does not feature redeclaration or overloading at least in 1.24)

So you have self.someField and super.someField which actually point to different things.


Grey Alien(Posted 2007) [#16]
basically it all comes down to testing. Test your type carefully so that you can see if it does what your expect, esp. where polymorphism is concerned.


FlameDuck(Posted 2007) [#17]
What you surely dont want is to ever allow a derived class to override Delete() for the TBase class and thus leave a memory leak ?.
It should be the responsability of the programmer to invoke the super objects constructor/destructor. Otherwise there is no way for the programmer to control what happens. What if you needed the memory in the super class to be freed first?

In either case I think it's worse to have a case where a destructor is not invoked at all, rather to have them invoked at random.

Thats how all / all used languages behave.
Rubbish. Name one.

basically it all comes down to testing.
While that's certainly nice, it would be preferable for it to come down to consistency.


TomToad(Posted 2007) [#18]
Rubbish. Name one.

C++


Edit to make C++ code resemble more the BMax code


marksibly(Posted 2007) [#19]
Hi,

TIO's Delete() is indeed doing nothing - well spotted!

Either Close() will need to be added to subclasses, or I could change the behaviour of New/Delete so they don't 'dynamically modify' object type as an object is being created/destroyed.

The actual reason for objects changing type while they are created/destroyed is basically so they don't call methods that may use or depend upon 'uninitialized' fields - uninitialized either because they haven't been constructed yet, or because they have just been destroyed.

This is important in C++ because fields aren't 'nulled' when an object is created. It's less important in Java/Max because fields aren't really ever 'uninitialized' - yet it still 'feels' right.

Will think about this one!


SculptureOfSoul(Posted 2007) [#20]
I think it'd be nice if by default a child class called it's parent/super class destructor, but if an option or function was provided to break out of that cycle. Thusly, people using others modules wouldn't have to make explicit calls to the parent of any derived types, but if they needed some crazy destructor sequence they could call the function or special language operator to signal that the destruction is complete and avoid the parent calls altogether.

This is sort of like Flameduck's request, although I'd rather see the explicit call only necessary in the rare circumstance where you truly need to override the parents destructor behavior.

Either way, it's not a big deal to me. I can't offhand think of any particular instance where I'd need to override a parents destructor behavior - and I'd imagine any scenario that came up could be worked around anyhow (and it would only come up due to poor object design, probably.)


marksibly(Posted 2007) [#21]
To clarify a bit:

When an object is created, first it's superclass New() method is called, then it's class New() etc. This is sensible as each superclass puts the object into a 'sensible' state before subclass New() is called.

When an object is destroyed, it's Delete() is called, then it's superclass Delete() etc. In other words, in the opposite order of creation. Also sensible.

However, the wrinkle is that an object's type is dynamically changed while the objects is being created/destroyed.

So when the base class New() is called, the object truly *is* of type Base. If New() calls any methods, then only the Base type version of that method is called - not any derived type version.

Ditto Delete() - by the time base type Delete() is called, the object is again of type Base and any calls to methods will only call the base type method. So TIO is indeed bugged.

This is logical, as once the derived type has finished Delete(), it is no longer of type 'Derived' - theoretically, it doesn't exist and it's fields have no meaning. Ditto before it's New() is reached.

C++ does this and I'm 99% sure Java does also, but I do agree it is confusing and wonder if it should be changed. On the other hand, the current system does mean that you can guarantee New() is exactly the first method ever called on your object, which is nice to know...

As for the field instances issue, this is the case with all OO languages I know of - Java/C++/ObjectiveC. Fields with the same name as a superclass field always have their own 'version' in memory which hides the superclass version - unless you use 'Super.' to access the superclass version. This too I find logical - you should not even being using superclass fields, and it could be argued that not even subclasses should see your fields! Fields are really just an implementation detail of your 'level' in the class hierarchy. Methods are what provide the abstraction.

Hope that clears things up a bit, but please note that Max is not particularly unusual in it's behaviour. Object creation/destruction is a subtly complex issue, as you're dealing with 'half created' objects during the process. With parameters and overloading, it's even more complex!


SculptureOfSoul(Posted 2007) [#22]

As for the field instances issue, this is the case with all OO languages I know of - Java/C++/ObjectiveC.


The only object oriented language I know of that doesn't do this is Lisp. In Lisp, only one instance of a field is inherited by a child when multiple instances exist in it's direct super classes. The instance inherited is based upon the first class listed as it's direct super-class - Lisp utilizes the order of the listed super classes as their order of precedence.

While that is fairly arbitrary, it does at least avoid multiple copies of the same field existing when a child is derived from two or more super classes that share some of the same fields.