Can I make MATLAB use a custom copy method when creating an array of my class?

7 views (last 30 days)
I'm having an issue with a class that has properties that are handle classes. I've created this example to demonstrate the problem:
classdef Problem_Class < handle
properties (SetObservable = true)
Prop1
end
methods
function s = Problem_Class
s.Prop1 = Prop1_Class;
end
end
end
classdef Prop1_Class < handle
properties (SetObservable = true)
Prop2
end
methods
function s = Prop1_Class
s.Prop2 = Prop2_Class;
end
end
end
classdef Prop2_Class < handle
properties (SetObservable = true)
Name (1,:) char
end
methods
function s = Prop2_Class
s.Name = '';
end
end
end
So, If I create an instance of my Problem_Class:
problem = Problem_Class
The issue arises if I now create an array of Prop1, for example:
problem.Prop1.Prop2.Name = 'index 1';
problem.Prop1(8).Prop2.Name = 'index 8';
problem.Prop1(7).Prop2.Name = 'index 7';
If we look at the .Name property for each problem.Prop1.Prop2, I'd hope to see this:
[problem.Prop1.Prop2]; {ans.Name}'
{'index 1'}
{1×0 char }
{1×0 char }
{1×0 char }
{1×0 char }
{1×0 char }
{'index 7'}
{'index 8'}
But instead I get this:
{'index 1'}
{'index 7'}
{'index 7'}
{'index 7'}
{'index 7'}
{'index 7'}
{'index 7'}
{'index 8'}
The reason this is happening is explained here: https://www.mathworks.com/help/matlab/matlab_oop/initializing-arrays-of-handle-objects.html, where it is stated (at the end) "results in two calls to the class constructor. The first creates the object for array element A(4,5). The second creates a default object that MATLAB copies to all remaining empty array elements."
So, when I call problem.Prop1(8).Prop2.Name = 'index 8'; MATLAB calls the Prop1_Class constructor once, to populate problem.Prop1(8), and then a second time to create a Prop1_Class object that it copies to problem.Prop1(2:7). So now problem.Prop1(2:7) all point to the same object in memory. I don't want this! I need problem.Prop1(2:7) to be unique elements, and I need this to happen implicitly rather than having to pre-initialise the problem.Prop1() array.
I thought I had found the solution here: https://www.mathworks.com/help/matlab/matlab_oop/custom-copy-behavior.html, - I created a class based on the HandleCopy example on that page, and made Problem_Class, Prop1_Class, and Prop2_Class all inherit from that, so that all of them should have the custom copy behaviour. I have verified that, e.g. a = copy(b), where "b" is an instance of the Prop1_Class, then behaves as expected, creating an independent copy of b in a (such that a~=b, but their properties have the same values). However, MATLAB does not appear to call the custom copy method when creating an array!
Going back to the previous example: when I call problem.Prop1(8).Prop2.Name = 'index 8'; once MATLAB has created the problem.Prop1(8) element, and then creates the second Prop1_Class object and "copies" that to problem.Prop1(2:7), how do I get MATLAB to use a custom copy function, so I can prevent all problem.Prop1(2:7) pointing at the same object?
Many thanks for reading this post, all help gratefully received!
  2 Comments
Peter O
Peter O on 26 Jun 2020
Hi Harry,
Let me start off by saying that MATLAB's OO's is not my strongest suit, and I might be suggesting what you've already tried in different words.
Is it necessary that Prop2 is also a handle class? I can get the desired result if instantiate a new Prop2 object during the copy and override the default copy options. I agree that you should be able to get a "fresh" Prop2 object for each in the fill-in which can retain handle's memory space advantages.
classdef Problem_Class < handle
properties (SetObservable = true)
Prop1
end
methods
function s = Problem_Class
s.Prop1 = Prop1_Class;
end
end
end
classdef Prop1_Class < matlab.mixin.Copyable & handle
properties (SetObservable = true)
Prop2
end
methods
function s = Prop1_Class
s.Prop2 = Prop2_Class;
end
end
methods(Access = protected)
% Override copyElement:
function cpObj = copyElement(obj)
cpObj = copyElement@matlab.mixin.Copyable(obj);
% New Init of Prop2
cpObj.Prop2 = Prop2_Class;
end
end
end
classdef Prop2_Class
properties (SetObservable = true)
Name % (1,:) char
end
methods
function s = Prop2_Class
s.Name = '';
end
end
end
Run
% Test Output
clear all
clear classes
A = Problem_Class;
A.Prop1.Prop2.Name = 'Index 1';
A.Prop1(8).Prop2.Name = 'Index 8';
A.Prop1(7).Prop2.Name = 'Index 7';
[A.Prop1.Prop2]; {ans.Name}'
A.Prop1(7).Prop2.Name = 'index 7, again';
[A.Prop1.Prop2]; {ans.Name}'
Gives
ans =
8×1 cell array
{'Index 1'}
{0×0 char }
{0×0 char }
{0×0 char }
{0×0 char }
{0×0 char }
{'Index 7'}
{'Index 8'}
ans =
8×1 cell array
{'Index 1' }
{0×0 char }
{0×0 char }
{0×0 char }
{0×0 char }
{0×0 char }
{'index 7, again'}
{'Index 8' }
I'll keep chewing on this though. There's either a way to do it, or you've stumbled on a bug or piece of undefined behavior that is deserving of a patch. :)
Harry Dymond
Harry Dymond on 26 Jun 2020
Hi Peter, thanks for the input. Yes, I need everything to be handles because I need to use (SetObservable = true) and that only works with handles.
If I understand correctly though, in your example above you've got it to work as I want, but by making Prop2_Class a value class rather than handle class. But you have (SetObservable = true) for the properties on a value class, and that's working for you? Did this change in a recent release (I'm on 2018a)?

Sign in to comment.

Accepted Answer

Ameer Hamza
Ameer Hamza on 26 Jun 2020
It seems like subsasgn() https://www.mathworks.com/help/matlab/ref/subsasgn.html could be helpful in this case. You can control the assignment behavior. The following code shows a very crude example. You can adapt and improve it according to your requirement
classdef Problem_Class < handle
properties (SetObservable = true)
Prop1
end
methods
function s = Problem_Class
s.Prop1 = Prop1_Class;
end
function A = subsasgn(A, S, B)
n = S(2).subs{:};
m = numel(A.Prop1);
num_new_elem = n-m;
if num_new_elem > 1
for i=1:num_new_elem-1
A.Prop1(m+i).Prop2.Name = '';
end
A.Prop1(m+num_new_elem).Prop2.Name = B;
else
A.Prop1(n).Prop2.Name = B;
end
end
end
end
Example
problem = Problem_Class;
problem.Prop1(1).Prop2.Name = 'index 1'; % the current definition of subsasgn requires to use index (1) for first element too
problem.Prop1(8).Prop2.Name = 'index 8';
problem.Prop1(5).Prop2.Name = 'index 7';
x = [problem.Prop1.Prop2];
{x.Name}'
Output
ans =
8×1 cell array
{'index 1'}
{1×0 char }
{1×0 char }
{1×0 char }
{'index 7'}
{1×0 char }
{1×0 char }
{'index 8'}
  2 Comments
Harry Dymond
Harry Dymond on 26 Jun 2020
Thanks so much! I think this has solved it (will need to check in my "real" code that is ... somewhat more complicated!) Fortunately I don't have to account for too many different scenarios; I have a class (let's call this "Main_Class"), where a couple of the properties are handle classes, and they can be arrays. Those classes themselves contain many properties, some of which are handle classes, some of which contain many properties, some of which are handle classes, etc. etc. nested about 5 deep at most. However, only properties of "Main_Class" are allowed to be arrays, so that should hopefully stop things from getting too complicated.
I tweaked your suggested code to make it a more general solution that should work for my "real" situation:
classdef Problem_Class < handle
properties (SetObservable = true)
Prop1 (1,:) Prop1_Class
end
methods
function s = Problem_Class
s.Prop1 = Prop1_Class;
end
function A = subsasgn(A, S, B)
try
if numel(S)>1 && S(2).type(1)=='('
n = S(2).subs{:};
m = numel(A.(S(1).subs));
numNewElem = n-m;
if numNewElem > 1
for i = 1:numNewElem-1
A.(S(1).subs)(m+i) = eval(class(A.(S(1).subs)));
end
end
end
A = builtin('subsasgn', A, S, B);
catch MEx
throwAsCaller(MEx);
end
end
end
end
Ameer Hamza
Ameer Hamza on 26 Jun 2020
Yes, the example in my answer was very crude. For a general case, several other things might need to be considered. Hopefully, a workaround would be added in a future release.

Sign in to comment.

More Answers (0)

Products


Release

R2018a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!