We all know the benefits of setting default class-property values: it saves coding, increases class readability, improves maintainability and reduces the potential for coding bugs due to uninitialized properties. Basically, we’re setting default values of the class properties, so that whenever a new instance of this class is created, it will be recreated with these same default property values. This is the behavior in any self-respecting OOP language, and is a well-entrenched paradigm in OOP computing. Simple enough, right?
Well, unfortunately it doesn’t behave quite this way in Matlab…
First, define class
Internal as follows:
classdef Internal < hgsetget & matlab.mixin.Copyable properties Value = 0 end methods function obj = Internal() end function set.Value(obj, newValue) obj.Value = newValue; end function str = char(obj) str = sprintf('Internal [Value=%s]', mat2str(obj.Value)); end function disp(obj) disp(char(obj)); end end methods (Static) function obj = getDefault() obj = Internal(); end end end
Now define class
External as follows:
classdef External < hgsetget & matlab.mixin.Copyable properties MyValue = Internal.getDefault; end methods function obj = External(varargin) % empty constructor end function set.MyValue(obj, newValue) obj.MyValue = newValue; end function str = char(obj) str = sprintf('External [MyValue = %s]', char(obj.MyValue)); end function disp(obj) disp(char(obj)); end end end
Now run the following (note the highlighted unexpected internal value):
>> clear classes >> e1 = External e1 = External [MyValue = Internal [Value=0]] >> e1.MyValue.Value = 1 e1 = External [MyValue = Internal [Value=1]] >> e2 = External % This returns a bad value of 1 (should be 0!) e2 = External [MyValue = Internal [Value=1]] >> e1 == e2 % this proves that e1~=e2 (as expected) ans = 0 >> Internal.getDefault % this proves that the default Internal value remains unchanged (as expected) ans = Internal [Value=0]
Basically, this shows that setting
e1.MyValue.Value=1 has also affected future class constructions, such that
e2=External (which should have created a new handle object with a default property value of 0) now returns the modified value 1.
How is this possible?
The answer lies in the fine print of the documentation:
Evaluation of property default values occurs only when the value is first needed, and only once when MATLAB first initializes the class. MATLAB does not reevaluate the expression each time you create a class instance.
In other words, when Matlab first loads the
External class code into memory (typically upon its first instance creation), it assigns the handle reference to a new
Internal instance to the class property. This same handle reference (memory pointer, in a broad sense) is used for all subsequent creations of
External class instances. So basically, all instances of
External share the same
This does not affect properties that are initialized to primitive (non-object) data, nor to value classes (as opposed to handle classes). The reason is that Matlab’s built-in COW (copy-on-write) mechanism ensures that we get a new copy of a value class whenever one of its properties is modified. However, this does not happen in handle classes, so modifying
Internal‘s property in one instance of
External also affects the other instances.
So yes, it’s fully documented (sort of). But confusing? Counter-intuitive? Unexpected? – you bet!
In recent releases, Matlab increasingly relies on MCOS (Matlab’s latest OOP incarnation) for its internal codebase. I venture a guess that if a poll was made among MathWorker developers, the majority (if not vast majority) of them are not aware of this fine detail. Certainly programmers who come from other programming languages would not expect this behavior. This raises a concern that any internal Matlab object that has properties which are handle objects might incur hard-to-trace bugs due to this behavior.
Workaround and plea for action
At the moment, the only workaround is to programmatically set the property’s default value to a new instance of the handle class in the classes constructor. So, in our case, we would modify
External‘s constructor as follows:
classdef External < hgsetget & matlab.mixin.Copyable properties MyValue % no misleading default value here! end methods function obj = External(varargin) % non-empty constructor obj.MyValue = Internal.getDefault; end...
I strongly urge MathWorks to modify this unexpected behavior (and certainly the documentation). I believe that the vast majority of MCOS users would welcome a change to the expected behavior, i.e., create a new
Internal object at instance creation time rather than just at class-load time. Moreover, I bet that it would save many internal bugs in Matlab’s own code, which is probably by far the largest MCOS codebase worldwide…
I’m the first one to complain about backward incompatibilities, but in this particular case I think that it’s not an important concern since users should never rely on this “feature” in their code – it would be bad programming for so many reasons.
Addendum April 11, 2015: Dave Foti (who heads MCOS development at MathWorks) and Sam Roberts (a former MathWorker) have provided important insight as to the reasons for this confusing behavior (read the comments below). Once we understand it, we can actually use it in interesting ways, to differentiate between class load-time defaults (in the
properties section), and class-instantiation defaults (in the constructor). As noted below, MathWorks could probably do a better job of alerting users to the potentially confusion behavior, using either an MLint editor warning, and/or a run-time console warning (preferably both).