HolgerJ 2015-06-25 - I was wondering whether objects can go out of scope and get deleted like they do in C++ (whenever the block ends) or in Java (when the reference count allows the garbage collection to remove it). At #EuroTcl2015 in Cologne we discussed possibilities, although objects in TclOO are commands and therefore are not tied to the block (or proc) where they have been created.
The reason for getting this behaviour is avoiding memory leaks. If an object gets created locally and is not being used after the proc ends, there is no point in keeping it. Early return due to errors or explicit return statements can easily leave objects alive and cause memory leaks. This can cause the same headache as an open file (which is closed automatically in C++ when the stream goes out of scope).
In many other cases, objects get created in procs (or methods) and get returned, which is called a factory pattern. These objects must not stop existing when the proc (or method) ends (for this case C++11 has the notions of a move constructor and a move assignment transferring ownership of all dynamic parts of the object, thus avoiding freeing of the object's memory).
The first way discussed was tying the object to a local variable which I present as follows:
#!/bin/sh #\ exec tclsh "$0" "$@" package require TclOO namespace import oo::* # Account class from https://wiki.tcl-lang.org/20200 plus a little method to return current values class create Account { constructor {{ownerName undisclosed}} { my variable total overdrawLimit owner set total 0 set overdrawLimit 10 set owner $ownerName } method deposit amount { my variable total set total [expr {$total + $amount}] } method withdraw amount { my variable {*}[info object vars [self]] ;# "auto-import" all variables if {($amount - $total) > $overdrawLimit} { error "Can't overdraw - total: $total, limit: $overdrawLimit" } set total [expr {$total - $amount}] } method transfer {amount targetAccount} { my variable total my withdraw $amount $targetAccount deposit $amount set total } method getInfo {} { my variable {*}[info object vars [self]] ;# "auto-import" all variables return "account of '$owner' has a total of $total." } destructor { my variable total if {$total} {puts "remaining $total will be given to charity"} } } ;# class Account proc destroyObject {objname args} { puts "destroyObject $objname, calling destructor" $objname destroy } # This procedure creates an object and saves its name in the variable a. # That variable goes out of scope at the end of the procecure (Tcl has proc # scope, not block scope - similar to JavaScript). # The trace calls destroyObject when a goes out of scope. proc p {} { set a [Account new "John Doe"] trace add variable a unset [list destroyObject $a] puts "** created object named '$a' **" puts "depositing 200" $a deposit 200 puts [$a getInfo] try { puts "withdrawing 60" $a withdraw 60 } on error e { puts stderr $e } puts [$a getInfo] } ;# proc p puts "back in main (from proc p) and end of script."
The second way discussed was using a try/finally block to make sure that the destroying is always called, even in case of an error or other kind of early return from the proc.
# The class is the same as above. # This procedure creates an object and saves its name in the variable a. # That variable goes out of scope at the end of the procecure (Tcl has proc # scope, not block scope - similar to JavaScript). # The finally part calls destroyObject when a goes out of scope. proc p {} { try { set a [Account new "John Doe"] puts "** created object named '$a' **" puts "depositing 200" $a deposit 200 puts [$a getInfo] puts "withdrawing 300" $a withdraw 300 return [$a getInfo] } finally { $a destroy } } ;# proc if [catch { puts [p] } err] { puts "error: $err" } puts "back in main (from proc p) and end of script."
Using a mixin to 'tie' an object to a proc scope. This seems to work:
oo::class create WithScope { constructor args { set name [regsub -all ":" [self class][self] "_"] uplevel 1 [list trace add variable $name {unset} "[namespace code {my destroy}];#"] next {*}$args } } oo::class create Test { mixin WithScope constructor args { puts "[self] constructor: args=<$args>" } destructor {puts "[self] destructor..."} } proc test {} { puts "[info level 0]: info commands a=<[info commands a]>" Test create a eins zwei drei puts "[info level 0]: info commands a=<[info commands a]>" } puts "BEFORE: info commands a=<[info commands a]>" test puts "AFTER: info commands a=<[info commands a]>"
For a simpler method, aspect suggests to create the object(s) in a namespace that is later deleted.
See Also: Metaclass that provides basic garbage collection for instances [L1 ]
See Also: from the tcl-core mailing list: Tcl Objects with value semantics [L2 ]
See Also: Playing Strong/Weak References
See Also: tcloo-ext [L3 ]
tclooh : Minimally extending the TclOO object system to provide more natural object variables and optional automatic object cleanup on variable unset.