我正在尝试使用这里概述的方法,将一个C++类包装成matlab mex包装器。基本上,我有一个初始化mex文件,它返回一个C++对象句柄:
handle = myclass_init()
我可以将这个句柄传递给另一个 mex 文件(例如
myclass_amethod
),该文件使用句柄调用类方法,最终传递到 myclass_delete
以释放 C++ 对象。retval = myclass_amethod(handle, parameter)
myclass_delete(handle)
为了方便使用,我已经将其封装在MATLAB类中:
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
问题:这在并行代码中不起作用
这在非并行代码中运行良好。但是,一旦我从parfor
内部调用它:
cls = myclass();
parfor i = 1:10
cls.amethod(i)
end
我遇到了一个段错误,因为在
parfor
循环中(每个工作进程中)复制了该类的副本,但由于每个工作进程都是单独的进程,因此C++对象实例未被复制,导致指针无效。我最初尝试检测每个类方法是否在parfor循环中运行,在这些情况下也重新分配C++对象。然而,由于没有办法检查当前工作进程是否已为对象分配了内存,这会导致多次重新分配,然后仅一次删除(当工作进程退出时),从而导致内存泄漏(请参见问题底部的附录以获取详细信息)。
尝试的解决方案:复制构造函数和使用
matlab.mixin.Copyable
在C ++中,处理这种情况的方法是使用复制构造函数(以便在复制包装MATLAB类时只重新分配一次C ++对象)。快速搜索引出了matlab.mixin.Copyable类类型,它似乎提供了所需的功能(即MATLAB句柄类的深层副本)。因此,我尝试了以下操作: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
测试如上所述的这个类,即:
cls = myclass();
parfor i = 1:10
cls.amethod(i)
end
输出结果如下:
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
换句话说,当为parfor生成工作者时,似乎没有调用复制构造函数。有没有人能指出我做错了什么,或者是否有某种方法可以实现所需的行为,即在复制MATLAB包装器类时重新初始化C++对象句柄?
备选方案:检测是否在 worker 上运行
仅供参考,这是我正在使用的另一种方法,在 worker 上重新分配:
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
和输出:
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
如上所示,当在工作线程中运行时确实会发生重新分配。但是,析构函数仅在每个工作线程中调用一次,无论C++类被重新分配了多少次,都会导致内存泄漏。
解决方案:使用
loadobj
以下代码有效:
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