Is there a good way here to allow users to both retain access control for properties and overload indexing methods?(matlab 2020a)

46 Views Asked by At

Assuming a member within a class has private access properties, i.e., GetAccess=private, if we use a "." type reference for that member in the overloaded subsref method, then the access property of that member becomes invalid at that point.

--------------------- So, is there a good way here to allow users to both retain access control for properties and overload indexing methods?


Using the builtin function cannot resolve this issue; please alternate between the code at lines 42 and 43. The fundamental reason is that setting GetAccess=private cannot prevent access within the class itself. Therefore, the code at line 42 effectively provides a backdoor for accessing object, rendering the GetAccess restriction ineffective, and this is not caused by subsref. Even if you don't use subsref, if you construct an MPolynom object within the class and assign the data from object.coef to another property, for example, object.xxx=object.coef, and then assign object.xxx externally, it can bypass the GetAccess restriction. After all, it cannot prevent class methods from accessing any class members.

classdef  MPolynom
    properties (SetAccess=public,GetAccess=private)
        coef;    %多项式系数
    end
    methods
        function obj = MPolynom(c)   %构造函数
            if isa(c,'MPolynom')
                obj.coef = c.coef;
            else
                obj.coef = c(:)';
            end
        end
        function plot(obj)    %重载plot函数
            r = max(abs(roots(obj.coef)));
            x = (-1.1:0.01:1.1)*r;
            y = polyval(obj.coef,x);
            plot(x,y);
            xlabel('X')
            ylabel('Y','Rotation',0)
            grid on
        end
        function r = plus(obj1,obj2)
            if ~isa(obj1,'MPolynom')       %如果第一个参数不是类MPolynom对象
                obj1 = MPolynom(obj1);    %创建一个类MPolynom对象obj1
            end
            if ~isa(obj2,'MPolynom')       %如果第二个参数不是类MPolynom对象
                obj2 = MPolynom(obj2);    %创建一个类MPolynom对象obj2
            end
            k = length(obj2.coef) - length(obj1.coef);   %计算两个阵列的长度差
            %创建一个类MPolynom对象作为返回值
            r = MPolynom([zeros(1,k) obj1.coef]+[zeros(1,-k) obj2.coef]);
        end
        function b = subsref(a,s)     %下标索引引用的实现
            switch s(1).type
               case '()'          %圆括号类型的引用
                    ind = s.subs{:};
                    class(a.coef)
                    b = polyval(a.coef,ind);  %返回多项式的值
                case '.'         %“.”类型的引用
                    switch s(1).subs
                        case 'coef'
                            b=builtin('subsref',a,s);
%                             b = a.coef;
                        case 'plot'     %由于方法plot没有返回值,这里单独列出访问过程
                            a.plot;
                        otherwise        %其他带返回值方法的引用
                            if length(s)>1
                                %b=subsref(a.coef,s);
                                b = a.(s(1).subs)(s(2).subs{:});  %带输入参数的方法引用
                            else
                                b = a.(s.subs);  %不带输入参数的方法引用
                            end
                    end
                otherwise
                    error('Specify value for x as obj(x)')
            end
        end
        function y = polyval(obj,x)   %计算多项式对象obj在x处的值
            y = polyval(obj.coef,x);
        end
    end
end

Use the code on line 43 and comment out the code on line 42.

classdef  MPolynom
    properties (SetAccess=public,GetAccess=private)
        coef;    %多项式系数
    end
    methods
        function obj = MPolynom(c)   %构造函数
            if isa(c,'MPolynom')
                obj.coef = c.coef;
            else
                obj.coef = c(:)';
            end
        end
        function plot(obj)    %重载plot函数
            r = max(abs(roots(obj.coef)));
            x = (-1.1:0.01:1.1)*r;
            y = polyval(obj.coef,x);
            plot(x,y);
            xlabel('X')
            ylabel('Y','Rotation',0)
            grid on
        end
        function r = plus(obj1,obj2)
            if ~isa(obj1,'MPolynom')       %如果第一个参数不是类MPolynom对象
                obj1 = MPolynom(obj1);    %创建一个类MPolynom对象obj1
            end
            if ~isa(obj2,'MPolynom')       %如果第二个参数不是类MPolynom对象
                obj2 = MPolynom(obj2);    %创建一个类MPolynom对象obj2
            end
            k = length(obj2.coef) - length(obj1.coef);   %计算两个阵列的长度差
            %创建一个类MPolynom对象作为返回值
            r = MPolynom([zeros(1,k) obj1.coef]+[zeros(1,-k) obj2.coef]);
        end
        function b = subsref(a,s)     %下标索引引用的实现
            switch s(1).type
               case '()'          %圆括号类型的引用
                    ind = s.subs{:};
                    class(a.coef)
                    b = polyval(a.coef,ind);  %返回多项式的值
                case '.'         %“.”类型的引用
                    switch s(1).subs
                        case 'coef'
%                             b=builtin('subsref',a,s);
                            b = a.coef;
                        case 'plot'     %由于方法plot没有返回值,这里单独列出访问过程
                            a.plot;
                        otherwise        %其他带返回值方法的引用
                            if length(s)>1
                                %b=subsref(a.coef,s);
                                b = a.(s(1).subs)(s(2).subs{:});  %带输入参数的方法引用
                            else
                                b = a.(s.subs);  %不带输入参数的方法引用
                            end
                    end
                otherwise
                    error('Specify value for x as obj(x)')
            end
        end
        function y = polyval(obj,x)   %计算多项式对象obj在x处的值
            y = polyval(obj.coef,x);
        end
    end
end

Both of the above code snippets yield the same result when executing the following command. Therefore, the effective way to enforce the GetAccess restriction is to directly use error('GetAccess=private') at line 42.

p=MPolynom([1,3,2,5]);
p.coef
2

There are 2 best solutions below

1
Cris Luengo On

If you want to overload () and/or {} indexing without affecting . indexing, you would start your subsref function with something like this:

if strcmp(s(1).type, '.')
    [varargout{1:nargout}] = builtin('subsref',obj,s);
end

This simply calls the builtin (default) implementation of indexing if the first indexing operation is a ..

Note that this takes care also of methods, such as obj.plot. It is not necessary to manually implement that.

Of corse you can also make this part of a standard switch statement:

function varargout = subsref(obj,s)
   switch s(1).type
      case '.'
         [varargout{1:nargout}] = builtin('subsref',obj,s);
      case '()'
         ...
      case '{}'
         ...
      otherwise
         error('Not a valid indexing expression')

Reference: https://www.mathworks.com/help/matlab/matlab_oop/code-patterns-for-subsref-and-subsasgn-methods.html


It turns out that implementing the dot indexing in this way breaks access protection. The solution in MATLAB R2021b and later is described in the other answer. For earlier versions, the common solution is to manually filter the dot indexing:

function varargout = subsref(obj,s)
   switch s(1).type
      case '.'
         if strcmp(s(1).subs, 'coef')
            error('Property COEF not user accessible.')
         end
         [varargout{1:nargout}] = builtin('subsref',obj,s);
      case '()'
         ...
      case '{}'
         ...
      otherwise
         error('Not a valid indexing expression')

Note also that from within class methods, indexing never calls the overloaded subsref function. So the above will not affect your ability to access the 'coef' property inside class methods.

0
Edric On

It's a real shame that you're stuck on R2020a, because in R2021b there was introduced a mixin helper matlab.mixin.indexing.RedefinesParen which is specifically designed to help you out in this case. From the reference page, here's the sort of thing you need to do:

classdef MyClass < matlab.mixin.indexing.RedefinesParen
    properties (Access=public)
        Label
    end
    methods (Access=protected)
        function varargout = parenReference(obj, indexOp)
            % reference
        end

        function obj = parenAssign(obj,indexOp,varargin)
            % assignment
        end

        function n = parenListLength(obj,indexOp,ctx)
            % instead of numel overloads
        end

        function obj = parenDelete(obj,indexOp)
            % paren deletion
        end
    end
end