Когда я могу передать дескриптор функции?
У меня есть функция для кэшированной оценки. В качестве одного из аргументов он принимает дескриптор функции. При некоторых обстоятельствах дескриптор функции недоступен, и я не совсем понимаю, почему. Пример ниже показывает, что меня озадачило:
>> A.a = @plus; feval(@A.a, 1, 1)
ans =
2
>> clear A
>> A.a.a = @plus; feval(@A.a.a, 1, 1)
Error using feval
Undefined function 'A.a.a' for input arguments of type 'double'.
Итак, если у меня есть дескриптор функции, хранящийся в качестве члена структуры, я могу передать его в порядке, если он имеет один уровень глубины, но не если он имеет два уровня. В моем реальном случае использования у меня есть структура D
который содержит много (117) экземпляров различных классов, так что я на самом деле stct.obj.meth
, где stct
это структура, obj
является экземпляром / объектом класса, и meth
это метод. Переходя @stct.obj.meth
не удается, но если я назначу A = stct.obj
затем прохождение @A.meth
преуспевает.
При каких условиях я могу передать дескриптор функции в качестве аргумента, чтобы он по-прежнему был доступен в стеке?
Изменить: хотя в случае использования выше, я мог бы просто удалить @
так как @plus
это уже дескриптор функции. Однако рассмотрим ситуацию здесь:
>> type cltest.m
classdef cltest < handle
methods
function C = mymeth(self, a, b)
C = a + b;
end
end
end
>> A.a = cltest();
>> feval(@A.a.mymeth, 1, 1)
Error using feval
Undefined function 'A.a.mymeth' for input arguments of type 'double'.
>> b = A.a;
>> feval(@b.mymeth, 1, 1)
ans =
2
В этом случае мне нужно @
до A.a.mymeth
...
3 ответа
Введение классов было большим делом для MATLAB. Настолько большие, что они до сих пор не работают должным образом. Ваш пример показывает, что доступ к структуре и доступ к методу класса конфликтуют, потому что им пришлось перегрузить значение точки '.' и не заставить его работать без проблем. Все более или менее работает нормально, когда вы вызываете методы класса явно по их имени на консоли MATLAB, например, в вашем примере >> Aamymeth(1,1). Но когда у вас есть какой-либо тип косвенного обращения, оно скоро сломается.
Вы пытались получить дескриптор функции >> @A.a.mymeth
, который MATLAB не может понять, вероятно, потому что он запутывается из-за смешанной структуры / класса. Попытка обойти использование str2func
тоже не работает. Опять же, это работает только для явного доступа к имени, как показано здесь. Это ломается для вашего примера, например >> str2func('b.mymeth')
, Это даже не работает внутри класса. Попробуйте другие указания и наблюдайте, как они терпят неудачу.
Кроме того, MATLAB не любит давать вам дескрипторы метода класса. Там нет функции для этого. Нет способа получить все дескрипторы функций за один раз или даже динамически по имени строки.
Я вижу три варианта здесь. Сначала попробуйте изменить программу, если это возможно. Должны ли эти функции находиться в classdef?
Во-вторых, следуйте обходному пути вашего или Nispio. Они оба создают временную переменную для хранения ссылки на экземпляр класса, чтобы создать несмешанный доступ к его методам-членам. Проблема в том, что они оба требуют явного именования функции. Вы должны явно поместить этот код для каждой задействованной функции. Нет способа абстрагироваться от этого.
В-третьих, обманывайте, выдавая дескрипторы методов вашего класса изнутри. Вы можете выдавать их в структуре.
classdef cltest < handle
methods
function C = mymeth(self, a, b)
C = a + b;
end
function hs = funhandles(self)
hs = struct('mymeth', @self.mymeth, ...
'mymeth2', @self.mymeth2);
end
end
end
Затем вы можете получить доступ к дескрипторам по имени, даже динамически.
>> A.a = cltest;
>> feval(A.a.funhandles.mymeth, 1, 1);
>> feval(A.a.funhandles.('mymeth'), 1, 1)
ans =
2
Но будьте осторожны, используя это, вы можете получить доступ к методам Access=private извне.
Попробуй это:
feval(@(varargin)A.a.mymeth(varargin{:}),1,1);
Это немного глупо, но это должно работать.
РЕДАКТИРОВАТЬ:
Это работает путем создания анонимной функции, которая принимает переменное число аргументов и сбрасывает эти аргументы в метод A.a.mymeth()
, Таким образом, вы на самом деле не передаете указатель на функцию Aamymeth, вы передаете указатель на функцию, которая вызывает A.a.mymeth
,
Альтернативный способ достижения того же самого без использования varargin
было бы:
feval(@(x,y)A.a.mymeth(x,y),1,1);
Это создает анонимную функцию, которая принимает два аргумента и передает их A.a.mymeth
,
<speculation>
Я думаю, что это должно быть присуще тому, как оператор обработки унарной функции @
работает. Парсер Matlab, вероятно, смотрит на @token
и решает, стоит ли token
является действительной функцией. В случае a.mymeth
это достаточно умно, чтобы решить, что mymeth
является членом a
, а затем верните соответствующий дескриптор. Тем не менее, когда он видит A.a.mymeth
это может обнаружить, что A
это не класс, и при этом A
есть член по имени a.mymeth
и, следовательно, действительная функция не найдена. Это подтверждается тем фактом, что это работает:
A.a.a = @plus; feval(A.a.a,1,1)
и это не так:
A.a.a = @plus; feval(@A.a.a,1,1)
</speculation>
Вы можете обойти это, введя отдельную функцию, которая исправляет @
Оператор не делает:
function h=g(f)
x = functions(f);
if ~strcmp(x.type, 'anonymous')
h = evalin('caller', ['@(varargin)' x.function '(varargin{:})']);
else
h = f;
end
end
Теперь для вашего примера:
>> feval(g(@A.a.mymeth), 1, 1)
ans =
2
>> feval(g(@b.mymeth), 1, 1)
ans =
2
Я думаю, что это будет иметь минимальное влияние на ваш код. Вы можете сделать его немного более элегантным, но менее надежным и / или читабельным. uplus
метод не определен для function_handle
класс, чтобы вы могли создать uplus.m
в папке @function_handle
где-то на вашем пути с этим содержанием:
function h=uplus(f)
x = functions(f);
if ~strcmp(x.type, 'anonymous')
h = evalin('caller', ['@(varargin)' x.function '(varargin{:})']);
else
h = f;
end
end
Теперь вам просто нужно использовать +@
вместо @
, Для ваших примеров:
>> feval(+@A.a.mymeth, 1, 1)
ans =
2
>> feval(+@b.mymeth, 1, 1)
ans =
2