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…
Problem example
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 Internal
object!
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.
Reference: G1308623
Related posts:
- Class object creation performance Performance aspects of Matlab class object creation are discussed, with specific suggestions. ...
- Getting default HG property values Matlab has documented how to modify default property values, but not how to get the full list of current defaults. This article explains how to do this. ...
- Class object tab completion & improper field names Tab completions and property access can be customized for user-created Matlab classes. ...
- Setting class property types Matlab's class properties have a simple and effective mechanism for setting their type....