scopetclitcl

how to access objects generated within other objects from outside


I wrote some container type which keep objects. I want to ensure that objects will be deleted if they are not longer needed.

I am able to generate objects inside my class and I can access these objects inside the class and also delete/destruct them.

But I can not access the objects from outside scope. What must be added to get access the elements?

#!/usr/bin/wish

package require itcl

namespace import itcl::*

class A { 
    private variable x

    public constructor { x_ } { 
        set x $x_ 
        puts "Create A"
        puts "this $this with value $x"
    }   

    public method Do { } { 
        puts "A::Do called" 
        puts "Value x $x"
    }   

    # Attention constructor did not have braces for parms even not empty ones!
    public destructor {
        puts "destruct insance of A, value of x $x"
    }   

    # Delete my self
    public method delete { } { 
        puts "delete object $this"
        # why we need to add :: to delete here 
        ::delete object $this
    }   
}

class OwningList {
    private variable objects

    public constructor { } { 
        set objects {} ; # create empty list
    }   

    public method Generate { classname args } { 
        set object [ $classname #auto $args ]
        lappend objects $object
    }   

    # attention: No braces for paremeters in destructor
    destructor {
        foreach el $objects { ::delete object $el }
    }   

    # using default parameter for parameter 2
    public method EraseByIndex { start_ { end_ -1 } } {
        if { $end_ == -1 } {
            ::delete object [lindex $objects $start_]
            set objects [lreplace $objects $start_ $start_]
        } else {
            for { set s $start_ } { $s <= $end_ } { incr s } {
                ::delete object [lindex $objects $s]
            }
            set objects [lreplace $objects $start_ $end_]
        }
    }

    public method GetList { } {
        return [itcl::scope objects]
    }

    public method Do { } {
        foreach n $objects {
            puts "Call function from class scope"
            $n Do  ; # works!
        }
    }

    public method CallAFunction { object function args } {
        puts "Execute: $object $function $args"
        eval $object $function $args
    }

}
OwningList ol;
ol Generate A 0
ol Generate A 1
ol Generate A 2
ol Generate A 3
ol Generate A 4
ol Generate A 5
ol Generate A 6

puts "Erase 5"
ol EraseByIndex 5
puts "erase 1 to 2"
ol EraseByIndex 1 2

ol Do

set x [ ol GetList ]
upvar 0 $x y
foreach n $y {
    puts "Got $n"
    puts "Want to do: $n Do"
    #$n Do    ; # <<<<<<<<<<<<<<<<<<<<<<<<<<<< this will not work! Error message see below
    ol CallAFunction $n Do
}

puts "Erase Container"
delete object ol

Error in startup script: invalid command name "a0"
    while executing
"$n Do"
    ("foreach" body line 4)
    invoked from within
"foreach n $y { 
    puts "Got $n" 
    puts "Want to do: $n Do"
    $n Do
    ol CallAFunction $n Do 
}"
    (file "./owning_list.tcl" line 100)

How can I fix this issue?


Solution

  • The problem is that you're iterating over a list of names that are local to the container's context. This is perhaps exacerbated by the fact that your GetList method returns the handle to the variable; while useful in some circumstances (attaching traces, debugging, etc.) that's probably not a good idea; it's typically not a good idea for things "outside" an object to manipulate the internal state of an object, at least as a design principle.

    What I'd do is alter GetList to return the contents of the list, processed so that each object is represented by its fully-qualified name. Probably this:

    public method GetList {} {
        return [lmap item $objects { namespace which $item }]
    }
    

    Then I'd iterate over them from outside like this:

    foreach item [o1 GetList] {
        $item Do
    }