object with scope

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.