Can I make MATLAB use a custom copy method when creating an array of my class?
6 views (last 30 days)
Show older comments
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
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. :)
Accepted Answer
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
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.
More Answers (0)
See Also
Categories
Find more on Matrix Indexing in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!