如何为输出参数为零的函数重载 subsref / numArgumentsFromSubscript?

How to overload subsref / numArgumentsFromSubscript for functions which have zero output arguments?

我想要一个包含 containers.Map 的 class。此外,我希望能够使用 () 索引从父 class 访问子地图,例如:

>> map = containers.Map('Foo', 'Bar');
>> ex = Example(map);
>> ex('Foo')

ans =

Bar

此代码如下,运行良好。我遇到的唯一问题是 class 上定义的其他方法。根据文档,我知道我需要覆盖 numArgumentsFromSubscript(不知何故?)来帮助 nargout。正如我在网上看到的那样,我粗略地尝试简单地使用 numel(obj) 在大多数情况下都有效,但当您调用的函数没有输出参数时(在这种情况下 numel(obj) == 1 ~= 0)。

使用下面的示例代码,

>> ex.outGoodbye

ans =

Goodbye

太棒了!然而,

>> ex.sayHello
Error using Example/sayHello
Too many output arguments.

Error in Example/subsref (line 17)
                 [varargout{1:nargout}] = builtin('subsref', obj, struct);

如何解决这个问题?

classdef Example
    % =====================================================================
    properties
        map
    end
    % =====================================================================
    methods
        % -----------------------------------------------------------------
        function obj = Example(map)
            obj.map = map;
        end
        % -----------------------------------------------------------------
        function varargout = subsref(obj, struct)
            if strcmp(struct(1).type, '()')
                 [varargout{1:nargout}] = builtin('subsref', obj.map, struct);
            else
                 [varargout{1:nargout}] = builtin('subsref', obj, struct);
            end
        end
        % -----------------------------------------------------------------
        function n = numArgumentsFromSubscript(obj, struct, indexingContext)
            n = numel(obj); % Necessary to overload subsref - for some reason
        end
        % -----------------------------------------------------------------
        function obj = subsasgn(obj, struct, varargin)
            if strcmp(struct(1).type, '()')
                obj = builtin('subsasgn', obj.map, struct, varargin{:});
                obj = Example(obj);
            else
                obj = builtin('subsasgn', obj, struct, varargin{:});
            end
        end
        % -----------------------------------------------------------------
        function sayHello(obj)
            disp('Hello'); % nargout == 0. Does NOT work
        end
        % -----------------------------------------------------------------
        function out = outGoodbye(obj)
            out = 'Goodbye'; % nargout > 0. Works
        end
        % -----------------------------------------------------------------
    end
    % =====================================================================
end

所以进一步深入研究这个问题,您有几个选项可以解决这个问题。

method(obj) 调用约定

您可以简单地更改调用 class 方法的方式。您可以简单地使用标准函数表示法,而不是使用点表示法。

sayHello(ex)

%// vs.

ex.sayHello()

这将避免在调用方法时调用 subsref。此外,这实际上是 MATLAB OOP 当前迭代中 class 的 fastest way to call a method。此外,这不需要更改您当前的代码。

使用nargout确定方法输出的数量

另一种选择是在 subsrefnumArgumentsFromSubscript 中添加一个特殊情况,以查找使用点表示法调用的方法。然后您可以使用 following callnargout.

显式确定方法的输出参数的数量
nArgs = nargout('classname>classname.methodname');

那么您将使用它而不是 numel(obj)。这可以在 numArgumentsFromSubscript subsref.

中实现

numArgumentsFromSubscript

function n = numArgumentsFromSubscript(obj, struct, indexingContext)
    %// Check if we are calling obj.method
    if strcmp(struct(1).type, '.') && ...
        ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))

        %// Determine the package (if any)
        cls = class(obj);
        parts = regexp(cls, '\.', 'split');

        %// Import the package (if any) just into the namespace of this method
        if numel(parts) > 1
            import(cls);
        end

        %// Determine number of outputs for this method
        n = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));
    else
        %// Default to numel(obj)
        n = numel(obj);
    end
end

subsref

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

    %// Check if we are calling obj.method
    elseif strcmp(struct(1).type, '.') && ...
           ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))

        %// Determine the package (if any)
        cls = class(obj);
        parts = regexp(cls, '\.', 'split');

        %// Import the package (if any) just into the namespace of this method
        if numel(parts) > 1
            import(cls);
        end

        %// Determine number of outputs for this method
        nout = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));         

        %// Call builtin subsref with this number of outputs
        [varargout{1:nout}] = builtin('subsref', obj, struct);

    else
        [varargout{1:nargout}] = builtin('subsref', obj, struct);
    end
end

总结

subsref 很痛苦,很多时候你最终会用很多逻辑来确定正在调用的是什么,而你只有大约 80% 的时间是正确的。我认为第一种方法是最直接的,可能是最高效的(您跳过 subsref 中的所有检查)并且可以更好地处理对象数组。

如果你真的想保留 obj.method 符号,我可能会建议更改 numArgumentsFromSubscript(而不是 subsref)以将输出参数的数量保留在一个地方。

更新

根据您的反馈,nargout 技巧在 class 包含在包中 (foo.bar.Example) 的情况下不起作用。我在上面的示例中添加了一个解决方法。诀窍是在调用 nargout('Example>Example.sayHello').

之前调用 import('foo.bar.Example')