tclincr-tcl

Tcl: Setting a private variable of an owned instance within a class


Suppose the following code declarations:

itcl::class ObjectA {
    private variable m_ownedObject
    private variable m_someVariable

   constructor {} \
   {
        set m_ownedObject [ObjectA #auto]
   }

   protected method SetSomeVariable {newVal} {
       set m_someVariable $newVal
   }

   public method SomeMethod{} {
       $m_ownedObject SetSomeVariable 5
   }
}

This is the only way I know how to modify m_someVariable from within SomeMethod on m_ownedObject. In other languages (say C/C++/C#/Java to name a few), I'm pretty sure I could just say something like:

m_ownedObject.m_someVariable = 5

Is there a way to do something like this in tcl, or do I always need to create protected getters and setters? Hopefully this is reasonably clear.


Solution

  • You cannot directly do what you're asking for in itcl. However, this being Tcl, you can work around that, and directly set the member variable from anywhere. I use a helper routine called memv which you pass an instance and a variable name, and it returns a "reference" to that variable.

    This obviously bypasses the private/protected mechanisms that Itcl set up, so you're violating abstractions using them. It's your call whether you want to use it. I find it invaluable for debugging, but don't it in production code.

    The example usage is:

    set [memv m_ownedObject m_someVariable] 5
    

    The code for memv is:

    proc memv {obj varname} {
      # have to look up the variable, which might be in a base class
      # so do 'info variable' to get that, and the full name is the 3rd element
    
      # next two lines handle pulling apart an array
      set aindex ""
      regexp -- {^(.+)\((.+)\)$} $varname ignore varname aindex
    
      set var [lindex [$obj info variable $varname] 2]
    
      if {$aindex == ""} {
        return [list @itcl $obj $var]
      } else {
        return [list @itcl $obj $var\($aindex\)]
      }
    }
    

    Similarly, I have a helper routine named memv which allows you to call any method (including private and protected methods). It's usage is similar

    [memf m_ownedObject SetSomeVariable] 5
    

    And it's code is:

    proc memf {obj fcnname} {
      set f [$obj info function $fcnname]
      if {[llength $f] != 5} {
        error "expected '$obj info function $fcnname' to return something like 'private proc ::namespace::name args {...}' but got: $f"
      }
      set fullname [lindex [$obj info function $fcnname] 2]
      set namespace [namespace qualifiers $fullname]
      set function [namespace tail $fullname]
      return [itcl::code -namespace $namespace $obj $function]
    }