I am trying to wrap a C ++ class in a matlab mex shell using the approach described below. Basically, I have a mex initialization file that returns a handle to a C ++ object:
handle = myclass_init()
Then I can pass this to another mex file (for example, myclass_amethod ), which uses a handle to call the methods of the class, and then to myclass_delete to free the C ++ object:
retval = myclass_amethod(handle, parameter) myclass_delete(handle)
I wrapped this in a MATLAB class for ease of use:
classdef myclass < handle properties(SetAccess=protected) cpp_handle_ end methods % class constructor function obj = myclass() obj.cpp_handle_ = myclass_init(); end % class destructor function delete(obj) myclass_delete(obj.cpp_handle_); end % class method function amethod(parameter) myclass_amethod(obj.cpp_handle_, parameter); end end end
Problem: this does not work in parallel code
This works fine in non-parallel code. However, as soon as I call it from parfor :
cls = myclass(); parfor i = 1:10 cls.amethod(i) end
I get segfault since the copy of the class is executed in the parfor loop (in each worker), but since each worker is a separate process, the C ++ object instance is not copied, which leads to an invalid pointer.
At first I tried to detect when each class was launched in a parfor loop, and in these cases redistributes the C ++ object. However, since there is no way to check whether the object was allocated for the current worker or not, this leads to several redistributions, and then only one deletion (when the worker exits), which leads to a memory leak (see the Appendix below for a question for details).
Attempted solution: copy constructors and use matlab.mixin.Copyable
In C ++, a way to handle this would be copy constructors (so that a C ++ object will be redistributed only once when the MATLAB wrapper class is copied). A quick search calls the class type matlab.mixin.Copyable , which seems to provide the required functionality (i.e., Deep copies of the MATLAB processing classes). So I tried the following:
classdef myclass < matlab.mixin.Copyable properties(SetAccess=protected) cpp_handle_ end methods function obj = myclass(val) if nargin < 1 % regular constructor obj.cpp_handle_ = rand(1); disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]); else % copy constructor obj.cpp_handle_ = rand(1); disp(['Copy initialized myclass with handle: ' num2str(obj.cpp_handle_)]); end end % destructor function delete(obj) disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]); end % class method function amethod(obj) disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]); end end end
testing this class as above, i.e.:
cls = myclass(); parfor i = 1:10 cls.amethod(i) end
Output Results:
Initialized myclass with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Class method called with handle: 0.65548 Deleted myclass with handle: 0.65548 Deleted myclass with handle: 0.65548 Deleted myclass with handle: 0.65548
In other words, it seems that the copy constructor is not called at birth for parfor workers . Does anyone have any guidance on what I'm doing wrong, or is there a way to achieve the desired behavior when reinitializing a C ++ object descriptor when copying a MATLAB wrapper class?
Alternative Approach: Discovery While Working
Just for reference, here is an alternative approach that I use to redistribute when an employee has:
classdef myclass < handle properties(SetAccess=protected) cpp_handle_ end methods function obj = myclass(val) obj.cpp_handle_ = rand(1); disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]); end % destructor function delete(obj) disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]); end % class method function amethod(obj) obj.check_handle() disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]); end % reinitialize cpp handle if in a worker: function check_handle(obj) try t = getCurrentTask(); % if 'getCurrentTask()' returns a task object, it means we % are running in a worker, so reinitialize the class if ~isempty(t) obj.cpp_handle_ = rand(1); disp(['cpp_handle_ reinitialized to ' num2str(obj.cpp_handle_)]); end catch e % in case of getCurrentTask() being undefined, this % probably simply means the PCT is not installed, so % continue without throwing an error if ~strcmp(e.identifier, 'MATLAB:UndefinedFunction') rethrow(e); end end end end end
and conclusion:
Initialized myclass with handle: 0.034446 cpp_handle_ reinitialized to 0.55625 Class method called with handle: 0.55625 cpp_handle_ reinitialized to 0.0048098 Class method called with handle: 0.0048098 cpp_handle_ reinitialized to 0.58711 Class method called with handle: 0.58711 cpp_handle_ reinitialized to 0.81725 Class method called with handle: 0.81725 cpp_handle_ reinitialized to 0.43991 cpp_handle_ reinitialized to 0.79006 cpp_handle_ reinitialized to 0.0015995 Class method called with handle: 0.0015995 cpp_handle_ reinitialized to 0.0042699 cpp_handle_ reinitialized to 0.51094 Class method called with handle: 0.51094 Class method called with handle: 0.0042699 Class method called with handle: 0.43991 cpp_handle_ reinitialized to 0.45428 Deleted myclass with handle: 0.0042699 Class method called with handle: 0.79006 Deleted myclass with handle: 0.43991 Deleted myclass with handle: 0.79006 Class method called with handle: 0.45428
As can be seen from the foregoing, redistribution really occurs when working in working condition. However, the destructor is called only once for each worker, regardless of how many times the C ++ class has been redistributed, resulting in a memory leak.
Solution: using loadobj
The following works:
classdef myclass < handle properties(SetAccess=protected, Transient=true) cpp_handle_ end methods(Static=true) function obj = loadobj(a) a.cpp_handle_ = rand(1); disp(['Load initialized encoder with handle: ' num2str(a.cpp_handle_)]); obj = a; end end methods function obj = myclass(val) obj.cpp_handle_ = rand(1); disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]); end % destructor function delete(obj) disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]); end % class method function amethod(obj) disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]); end end end