New to Matlab but forced to use it for a new work project... I'm coming from a C++ background, but maybe I should not try to emulate coding paradigms from C++ with Matlab. I'm trying to figure out how to have a abstract class property that gets initialized when the constructor is called with a concrete member. For instance:
%AbstractClass.m
classdef (Abstract) AbstractClass < handle
end
%ConcreteClass.m
classdef ConcreteClass < AbstractClass
end
%myclass.m
classdef myclass < handle
properties
% X {AbstractClass} % would like to have this be like C++ nullptr
%X {AbstractClass} % can't initialize...
%X % Works, but not safe
X {mustBeA(X, 'AbstractClass')} % Suggested, error below
end
methods
% constructor
function obj = myclass( concreteInstanceOfAbstractClass )
obj.X = concreteInstanceOfAbstractClass
end
end
end
But when I try to run this
Y = ConcreteClass()
mustBeA(AbstractClass, Y) % no error, fine
mustBeA(ConcreteClass, Y) % no error, fine
A = myclass(Y) % throws error below
I get the following error
>> A = myclass(Y)
Error using implicit default value of property 'X' of class 'myclass'. Value must be one of the following types: 'AbstractClass'.
Is there a way to initialize the class in the constructor using the concrete instance of Abstract class? I have done some searching, but I'm new enough to Matlab that I'm probably not looking in the right places. This would be a very common thing to do in C++.
Try
properties
X {mustBeA(X, 'AbstractClass')}
end
which is a property validator rather than a class constraint.
Further to comments below, I realise the problem is that MATLAB insists on having a default value in the classdef. Unfortunately, this makes things difficult. One approach is to supply a valid concrete class as the default value. This works:
properties
X AbstractClass = ConcreteClass
end
but requires that the using class knows the name of ConcreteClass. Another approach (especially if the property needs to be set only at construction time) is this:
classdef Uses
properties (SetAccess = immutable)
% No constraints here
Property
end
methods
function obj = Uses(val)
arguments
% Enforce constraints here - scalar instance
% of something inheriting from AbstractClass
val (1,1) AbstractClass
end
obj.Property = val;
end
end
end
Related
TL;DR
I am having an issue with the arguments functionality, wherein a subclass fails to instantiate if any name-value argument pairs are passed to it.
(Class definitions are given at the end.)
The arguments functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public class properties, I got the following error:
Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs.
before any subclass code is even executed. This message is confusing, because tab-completion appears to work as expected for NV pairs:
Moreover, if no arguments are passed at all, everything works fine:
>> FluidLayer()
ans =
FluidLayer with properties:
prop1: NaN
prop2: NaN
vs.
>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...
Questions:
Why am I getting an error? I don't think I'm using the arguments mechanism in some unintended or undocumented way, so I'm doubly puzzled about what I might be doing wrong (assuming this is not a bug).
What can be done to resolve this, other than abandoning the whole arguments approach (I would like to keep argument name suggestions)? I have considered transitioning to varargin and/or using the functionSignatures.json approach - but these require significantly more work.
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = protected)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % methods
end
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
function layerObj = FluidLayer(props)
arguments
props.?FluidLayer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
% Custom modifications:
end % constructor
end % methods
end
I have been able to simplify your example to this:
classdef Layer
properties (GetAccess = public, SetAccess = protected)
prop1
prop2
end % properties
methods
function layerObj = Layer(props)
arguments
props.?Layer
end
disp(props)
end % constructor
end % methods
end
Now Layer('prop1',1) throws an error as you describe.
Thus, it has nothing to do with subclassing or inheritance.
However, if we remove the SetAccess = protected restriction (leaving the properties with public get and set access), then it all works as you expected.
I don't know why restricting set access would limit this use case, as it has nothing to do with writing those properties, and a class method should have set access anyway. My guess is that this is a bug.
The documentation of R2020a states that the structName.?ClassName syntax can only be used with "settable properties defined by a class (that is, all properties with public SetAccess)". Thus, using it with protected properties is explicitly unsupported.
As such, if we want to use the "automatic" arguments validation mechanism, we have no choice but to set the SetAccess to public. However, this solution exposes the object properties to unwanted external changes, and so a workaround is suggested based on several principles:
properties now have public SetAccess, as required by the documentation.
Custom setters are added that perform access validation based on dbstack and meta.class comparison.
New Layer.m (notice the 2 new methods blocks):
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = public)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
%% Constructor
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % protected methods
%% Setters & Getters
methods
function set.prop1(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop1 = val;
else
Layer.throwUnprotectedAccess();
end
end
function set.prop2(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop2 = val;
else
Layer.throwUnprotectedAccess();
end
end
end % no-attribute methods
%% Pseudo-protected implementation
methods (Access = protected, Static = true)
function throwUnprotectedAccess()
stack = dbstack(1);
[~,setterName,~] = fileparts(stack(1).name);
throw(MException('Layer:unprotectedPropertyAccess',...
['Unable to set "', stack(1).name(numel(setterName)+2:end),...
'", as it is a protected property!']));
end
function mc = getCallerMetaclass()
stack = dbstack(2, '-completenames');
if isempty(stack)
mc = ?meta.class;
else
[~,className,~] = fileparts(stack(1).file);
mc = meta.class.fromName(className);
end
end
end % protected static methods
end % classdef
New FluidLayer.m (the foo method was added):
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
%% Constructor
function layerObj = FluidLayer(props)
arguments
props.?Layer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
end % constructor
function obj = foo(obj)
obj.prop1 = obj.prop1 + 1;
end
end % methods
end % classdef
Here's a demonstration of how it works:
>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl =
FluidLayer with properties:
prop1: 2
prop2: 1
>> fl.prop1 = 5; % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
Layer.throwUnprotectedAccess();
>> fl.foo() % attempting to set property through method (intended)
ans =
FluidLayer with properties:
prop1: 3
prop2: 1
>>
In conclusion: it is possible to overcome the SetAccess = public limitation using setter methods, but convoluted modifications to class code are required.
Practical Notes:
The getCallerMetaclass is limited in that it doesn't correctly identify packages - resulting in potentially empty metaclass objects. So keep in mind that this function should be modified if that is the case.
dbstack is called multiple times, although it is not necessary (it can be called once in the setter and the result can then be passed around).
The setter code for different properties is 5-lines long and mostly replicated (with the exception of the property name) - this can be improved by grabbing the property name through dbstack.
I have an abstract handle class Mesh. It has several concrete subclasseses: Mesh1D, Mesh2D, Mesh3D. I want to write a function which differs only minimally for Mesh1D, Mesh2D and Mesh3D. Therefore, to avoid code redundancy, I am thinking of writing that function in the superclass and then call this function from within the subclasses. The simplified case:
Mesh.m
classdef (Abstract) Mesh < handle
properties (Abstract)
prop;
end
methods (Abstract)
% Some abstract methods
end
methods
function assemble(obj, other_parameters)
% Do something
end
end
end
Mesh1D
classdef Mesh1D < Mesh
properties
prop;
% Other properties
end
methods
function assemble(obj)
% Preprocessing steps
assemble#Mesh(other_parameters) % call the superclass method
% Postprocessing steps
end
end
However, it produces the error: 'assemble' is not an accessible method of base class 'Mesh'. What is the problem?
I have a large set of small, related classes linked together by an interface class. All classes implement a static method, which retrieves and processes data specific to the class.
The output of that static method needs to be formatted in at least 2 ways. Since the transformation of one format to the other is always the same and rather trivial (though long), I thought I'd implement it as a concrete, sealed, static method in the superclass.
However, then I run into the following problem:
% (in Superclass.m)
classdef SuperClass < handle
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Get data here
[arg1, arg2] = (???).subsStaticMethod
% transform data here
% ...
end
end
end
% (in Subclass.m)
classdef SubClass < SuperClass
methods (Static)
function [arg1, arg2] = subsStaticMethod
% Get class-specific data here
% ...
end
end
end
As far as I can see, calling SubClass.supersStaticMethod() is impossible with this design, because static methods need to be called using the class name explicitly. In other words, there is no way to insert the subclass name instead of the (???) in SuperClass.supersStaticMethod above.
Things I've tried:
mfilename('class') this doesn't work, because this always returns 'SuperClass'
dbstack does not contain the information that the method is actually being called from a subclass
I know that I can work around this problem by making supersStaticMethod non-static, and calling the method on a temporary instance (like SubClass().supersStaticMethod()). Or create a small wrapper method inside each subclass that just calls the superclass method with mfilename('class') as argument. Or any of a 100 other things that seem equally clumsy.
But I'd really like to know if there is some meta.class trickery or something that can cleanly solve this problem. All I've found is this dated thread, which processes the MATLAB command line programmatically to get the subclass name.
However, my classes are to be used inside scripts/functions, and command line use is going to be for debugging purposes only...
Any ideas?
Below's my hacky proposal. The idea is that you store the current calling class in a "static variable" of SuperClass, then query this field and use it in feval to call the correct subclass' method. Several notes:
It could only work as long as you're not doing some computations in parallel (i.e. calling SubClass#.supersStaticMethod from more than one thread at a time under a shared memory architecture - in which case the calling class field will be overwritten erratically).
SuperClass's supersStaticMethod cannot be Sealed, though the subclasses' versions can.
The "static variable" is cleared after SubClass.supersStaticMethod to ensure that this method is only ever called from a subclass.
I've added matlab.mixin.Heterogeneous as superclass of SuperClass for the purpose of demonstration.
classdef SuperClass < handle & matlab.mixin.Heterogeneous
properties (Abstract = true, Access = private, Constant = true)
subclass#meta.class scalar;
end
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function out = currentClass(input) % < the closest thing in MATLAB to a static variable
persistent currentClass;
if nargout == 0 && nargin == 1 % "setter" mode
currentClass = input;
out = [];
else % "getter" mode
out = currentClass;
end
end
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Who am I?
whosCalling = SuperClass.currentClass();
if isempty(whosCalling) || ~isa(whosCalling,'meta.class')
[other_arg1,other_arg2] = deal(NaN);
return
else
whosCalling = whosCalling.Name;
end
fprintf(1,'\nCalled from: %s\n', whosCalling);
% Get data here
[arg1, arg2] = feval([whosCalling '.subsStaticMethod']);
% transform data here
other_arg1 = arg1+arg2; other_arg2=[arg1(:);arg2(:)];
fprintf(1,'other_arg1: %s, other_arg2: %s\n',...
num2str(other_arg1), mat2str(other_arg2));
% Clear the current class
SuperClass.currentClass([]);
end
end
end
classdef SubClass1 < SuperClass
properties (Constant)
subclass#meta.class scalar = ?SubClass1;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass1.subclass);
[other_arg1, other_arg2] = supersStaticMethod#SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = -1; arg2 = -2;
end
end % static methods
end % classdef
classdef SubClass2 < SuperClass
properties (Constant)
subclass#meta.class scalar = ?SubClass2;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass2.subclass);
[other_arg1, other_arg2] = supersStaticMethod#SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = 1; arg2 = 2;
end
end % static methods
end % classdef
Then you can test it like this:
function q31269260
arr = [SubClass1, SubClass2];
for ind1 = 1:numel(arr)
arr(ind1).supersStaticMethod;
end
% arr.supersStaticMethod would not work because elements are treated as "instances" of
% SuperClass, whose supersStaticMethod should not be called directly.
The output is:
Called from: SubClass1
other_arg1: -3, other_arg2: [-1;-2]
Called from: SubClass2
other_arg1: 3, other_arg2: [1;2]
I have a class that stores a figure handle. With the new Matlab handle graphics hg2 I get a "handle to deleted figure" error.
classdef mytestclass
properties
hFig = figure
end
end
Creating just one instance of the class works fine, I get a.hFig as a valid figure handle.
a = mytestclass % this will open the figure
But when I close the figure, and create another instance of the class, I get
b = mytestclass % this won't open any figure
b.hFig % this is now a handle to a deleted figure
Am I doing something wrong with classes? Or is this a bug?
I tried your example on Matlab 2009a (long before the new HG2) and the behavior is strictly the same as you describe.
it seems you are doing something slightly wrong with the way classes work in Matlab.
Basically you can assign the default value of a property with any type of numeric/text value:
properties
myProp %// No default value assigned
myProp = 'some text';
myProp = sin(pi/12); %// Expression returns default value
end
but do not assign them with a handle to something
myProp1 = figure ; %// all the object of this class will always point to this same figure
myProp2 = plot([0 1]) ; %// all the object of this class will always point to this same line object
otherwise all the objects of your class (even newly created) will point to the same actual handle which was created only once when your first object was instantiated.
If you want to generate a different graphic object (figure) each time you create a new object of your class, you have to generate it in the class constructor.
So your class become:
classdef mytestclass
properties (SetAccess = private) %// you might not want anybody else to modify it
hFig
end
methods
function obj = mytestclass()
obj.hFig = handle( figure ) ; %// optional. The 'handle' instruction get the actual handle instead of a numeric value representing it.
end
end
end
from the help:
Initializing Properties to Unique Values
MATLAB assigns properties to the specified default values only once
when the class definition is loaded. Therefore, if you initialize a
property value with a handle-class constructor, MATLAB calls this
constructor only once and every instance references the same handle
object. If you want a property value to be initialized to a new
instance of a handle object each time you create an object, assign the
property value in the constructor.
If I run this code to create a simple class:
classdef myclass
properties
m = 2;
n = m + 2;
end
end
I get an error:
Undefined function or variable 'm'.
Error in myclass (line 1)
classdef myclass
Why is this? I left out the constructor in this minimal example because a) the error still occurs if I put the constructor in, and b) I encountered this error in a unit testing class, and the constructor isn't called in such classes in MATLAB 2013b.
There is a note on this page that might explain the problem:
Note: 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.
I take this to mean that when you create a class instance, m is not yet initialized, hence you cannot use it to set the default for another property n.
The only way I can get it to work is if I declare m as a Constant property:
classdef myclass
properties (Constant = true)
m=2;
end
properties
n = myclass.m + 2;
end
end
But that probably doesn't help if you want to change m.
You could also move the initialization to the constructor:
classdef myclass
properties
m = 2;
n;
end
methods
function obj = myclass(obj)
obj.n = obj.m + 2;
end
end
end
MATLAB defines the properties as classname.propertyname. Hence, if you change the code to the following, it should work.
classdef myclass
properties
m = 2;
n = myclass.m + 2;
end
end
Kind regards,