如何使用来自 matlab 数据 api 结构的可变数量的字段在 C++ 中初始化对象
How can I initialize an object in C++ using a variable number of fields from a matlab data api struct
我正在使用 matlab c++ data api 构建一个 matlab MEX 函数。我的 mex 函数接受一个具有不同大小、类型和名称的字段的结构作为输入。这个结构的确切组成可能会有所不同,并且在程序范围之外定义,但我提前知道组成字段的所有可能组合。我们将此参数称为 'Grid',并将 Grid 传递给辅助函数。
在此辅助函数中,我想生成派生 class 的实例,其中派生 class 的特定类型将取决于 on/correspond 的特定组合网格的字段。这个想法是我可以提取 Grid 的字段并使用它们来创建正确派生的实例 class。我想实现这一点,而无需在每次添加具有不同可能的字段组合的新派生 class 时重写我的代码。我怎么能这样做?我也愿意接受其他方法和策略。
例如,Grid 可能在 matlab 环境中定义为:
Grid = struct('XPSF',X(:),'ZPSF',Z(:),'xe',Trans.ElementPos(:,1)*wvlToM,'TXDelay',TXdelay(:,8));
然后由 mex 函数处理并传递给定义如下的辅助函数:
void extractFields(matlab::data::StructArray& Grid);
目前,Grid也可以由单个值组成来代替XPSF或ZPSF。我预计将来可能会向 Grid 添加其他字段。对于这些可能的组合中的每一个,我都有一个派生的 class,它具有一些独特的重写函数:
class Beamform {
public:
//Constructor
Beamform();
virtual ~Beamform() {}
template <typename T> int sgn(T val) { return (T(0) < val) - (val < T(0)); }
virtual void calcIDXT(...);
};
class bfmPlaneWave : public Beamform
{
public:
double theta;
Eigen::VectorXd xe, XPSF, ZPSF, dTX;
template<typename Derived>
bfmPlaneWave(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
class bfmXRflMTX : public Beamform {
public:
double theta, zCoord;
Eigen::VectorXd xe, XPSFin, XPSFout, dTX;
template<typename Derived>
bfmXRflMTX(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
class bfmZRflMTX : public Beamform {
public:
double theta, xCoord;
Eigen::VectorXd xe, ZPSFin, ZPSFout, dTX;
template<typename Derived>
bfmXRflMTX(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
我首先要声明一个通用的构建模式。像这样:
class Beamform
{
public:
virtual void calcIDXT(...) = 0;
virtual ~Beamform() = default;
};
class bfmPlaneWave: public Beamform
{
public:
/** Return nullptr if not compatible */
static bfmPlaneWave* fromGrid(matlab::data::StructArray&);
virtual void calcIDXT(...) override;
};
class bfmXRflMTX: public Beamform
{
public:
/** Return nullptr if not compatible */
static bfmXRflMTX* fromGrid(matlab::data::StructArray&);
virtual void calcIDXT(...) override;
};
然后你可以有一个简单的中央工厂函数,你可以根据需要扩展它:
/**
* Option 1: Use a central dispatch function which can be updated with a
* simple two-liner
*/
std::unique_ptr<Beamform> beamformForGrid(matlab::data::StructArray& Grid)
{
std::unique_ptr<Beamform> rtrn;
if(rtrn.reset(bfmPlaneWave::fromGrid(Grid)), rtrn != nullptr)
return rtrn;
if(rtrn.reset(bfmXRflMTX::fromGrid(Grid)), rtrn != nullptr)
return rtrn;
// insert more here
return rtrn;
}
但是,如果我没有理解错的话,这是你不想要的。在那种情况下,您可以使用中央注册表和全局构造函数。加载 DLL 时,全局构造函数(用于全局变量的构造函数)为 运行。这类似于 CppUnit 注册其单元测试的方式。
class AbstractBeamformFactory
{
public:
virtual ~AbstractBeamformFactory() = default;
/** Return nullptr if not compatible */
virtual Beamform* fromGrid(matlab::data::StructArray&) = 0;
};
/**
* Registers existing factories
*
* Follows the singleton pattern.
* Yes, it is frowned upon, but if it works, it works.
*/
class BeamformRegistry
{
/**
* Protects the list of factories
*
* A bit overkill seeing how neither Matlab nor global constructors are
* particularly multithreaded, but better safe than sorry
*/
mutable std::mutex mutex;
std::vector<AbstractBeamformFactory*> factories;
public:
/**
* Retrieves singleton instance
*
* This isn't a global variable because we need to use it in other
* global constructors and we can't force a specific order between
* global constructors
*/
static BeamformRegistry& globalInstance();
void add(AbstractBeamformFactory* factory)
{
std::lock_guard<std::mutex> lock(mutex);
factories.push_back(factory);
}
void remove(AbstractBeamformFactory* factory)
{
std::lock_guard<std::mutex> lock(mutex);
factories.erase(std::find(factories.begin(), factories.end(), factory));
}
std::unique_ptr<Beamform> beamformForGrid(matlab::data::StructArray& Grid) const
{
std::unique_ptr<Beamform> rtrn;
std::lock_guard<std::mutex> lock(mutex);
for(AbstractBeamformFactory* factory: factories)
if(rtrn.reset(factory->fromGrid(Grid)), rtrn != nullptr)
break;
return rtrn;
}
};
/**
* Implements AbstractBeamformFactory for a specific type of beamformer
*
* Create a global variable of this type in order to add it to the global
* BeamformRegistry
*/
template<class BeamformImplementation>
class BeamformFactory: public AbstractBeamformFactory
{
bool registered;
public:
explicit BeamformFactory(bool registerGlobal=true)
: registered(registerGlobal)
{
/* don't move this to the base class to avoid issues around
* half-initialized objects in the registry
*/
if(registerGlobal)
BeamformRegistry::globalInstance().add(this);
}
virtual ~BeamformFactory()
{
if(registered)
BeamformRegistry::globalInstance().remove(this);
}
virtual Beamform* fromGrid(matlab::data::StructArray& Grid) override
{ return BeamformImplementation::fromGrid(Grid); }
};
/* in CPP files */
BeamformRegistry& BeamformRegistry::globalInstance()
{
static BeamformRegistry instance;
return instance;
}
/*
* Make global variables to add entries to registry.
* These can be scattered across different cpp files
*/
BeamformFactory<bfmPlaneWave> planeWaveFactoryInstance;
BeamformFactory<bfmXRflMTX> XRflMTXFactoryInstance;
现在您可以简单地调用 BeamformRegistry::globalInstance().beamformForGrid(Grid)
来访问所有已注册的波束形成实现并扩展实现的数量,您只需将工厂实例分散到您的 cpp 文件中。
我不确定的一件事是它如何与 MEX 交互。 Matlab 什么时候加载它的扩展?如果这只是以某种形式的惰性方式发生,那么全局构造函数可能不会足够快地执行。我想用一些打印语句来检查是值得的。
我正在使用 matlab c++ data api 构建一个 matlab MEX 函数。我的 mex 函数接受一个具有不同大小、类型和名称的字段的结构作为输入。这个结构的确切组成可能会有所不同,并且在程序范围之外定义,但我提前知道组成字段的所有可能组合。我们将此参数称为 'Grid',并将 Grid 传递给辅助函数。
在此辅助函数中,我想生成派生 class 的实例,其中派生 class 的特定类型将取决于 on/correspond 的特定组合网格的字段。这个想法是我可以提取 Grid 的字段并使用它们来创建正确派生的实例 class。我想实现这一点,而无需在每次添加具有不同可能的字段组合的新派生 class 时重写我的代码。我怎么能这样做?我也愿意接受其他方法和策略。
例如,Grid 可能在 matlab 环境中定义为:
Grid = struct('XPSF',X(:),'ZPSF',Z(:),'xe',Trans.ElementPos(:,1)*wvlToM,'TXDelay',TXdelay(:,8));
然后由 mex 函数处理并传递给定义如下的辅助函数:
void extractFields(matlab::data::StructArray& Grid);
目前,Grid也可以由单个值组成来代替XPSF或ZPSF。我预计将来可能会向 Grid 添加其他字段。对于这些可能的组合中的每一个,我都有一个派生的 class,它具有一些独特的重写函数:
class Beamform {
public:
//Constructor
Beamform();
virtual ~Beamform() {}
template <typename T> int sgn(T val) { return (T(0) < val) - (val < T(0)); }
virtual void calcIDXT(...);
};
class bfmPlaneWave : public Beamform
{
public:
double theta;
Eigen::VectorXd xe, XPSF, ZPSF, dTX;
template<typename Derived>
bfmPlaneWave(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
class bfmXRflMTX : public Beamform {
public:
double theta, zCoord;
Eigen::VectorXd xe, XPSFin, XPSFout, dTX;
template<typename Derived>
bfmXRflMTX(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
class bfmZRflMTX : public Beamform {
public:
double theta, xCoord;
Eigen::VectorXd xe, ZPSFin, ZPSFout, dTX;
template<typename Derived>
bfmXRflMTX(double& angle, ...);
template<typename Derived>
void calcIDXT(...) override;
};
我首先要声明一个通用的构建模式。像这样:
class Beamform
{
public:
virtual void calcIDXT(...) = 0;
virtual ~Beamform() = default;
};
class bfmPlaneWave: public Beamform
{
public:
/** Return nullptr if not compatible */
static bfmPlaneWave* fromGrid(matlab::data::StructArray&);
virtual void calcIDXT(...) override;
};
class bfmXRflMTX: public Beamform
{
public:
/** Return nullptr if not compatible */
static bfmXRflMTX* fromGrid(matlab::data::StructArray&);
virtual void calcIDXT(...) override;
};
然后你可以有一个简单的中央工厂函数,你可以根据需要扩展它:
/**
* Option 1: Use a central dispatch function which can be updated with a
* simple two-liner
*/
std::unique_ptr<Beamform> beamformForGrid(matlab::data::StructArray& Grid)
{
std::unique_ptr<Beamform> rtrn;
if(rtrn.reset(bfmPlaneWave::fromGrid(Grid)), rtrn != nullptr)
return rtrn;
if(rtrn.reset(bfmXRflMTX::fromGrid(Grid)), rtrn != nullptr)
return rtrn;
// insert more here
return rtrn;
}
但是,如果我没有理解错的话,这是你不想要的。在那种情况下,您可以使用中央注册表和全局构造函数。加载 DLL 时,全局构造函数(用于全局变量的构造函数)为 运行。这类似于 CppUnit 注册其单元测试的方式。
class AbstractBeamformFactory
{
public:
virtual ~AbstractBeamformFactory() = default;
/** Return nullptr if not compatible */
virtual Beamform* fromGrid(matlab::data::StructArray&) = 0;
};
/**
* Registers existing factories
*
* Follows the singleton pattern.
* Yes, it is frowned upon, but if it works, it works.
*/
class BeamformRegistry
{
/**
* Protects the list of factories
*
* A bit overkill seeing how neither Matlab nor global constructors are
* particularly multithreaded, but better safe than sorry
*/
mutable std::mutex mutex;
std::vector<AbstractBeamformFactory*> factories;
public:
/**
* Retrieves singleton instance
*
* This isn't a global variable because we need to use it in other
* global constructors and we can't force a specific order between
* global constructors
*/
static BeamformRegistry& globalInstance();
void add(AbstractBeamformFactory* factory)
{
std::lock_guard<std::mutex> lock(mutex);
factories.push_back(factory);
}
void remove(AbstractBeamformFactory* factory)
{
std::lock_guard<std::mutex> lock(mutex);
factories.erase(std::find(factories.begin(), factories.end(), factory));
}
std::unique_ptr<Beamform> beamformForGrid(matlab::data::StructArray& Grid) const
{
std::unique_ptr<Beamform> rtrn;
std::lock_guard<std::mutex> lock(mutex);
for(AbstractBeamformFactory* factory: factories)
if(rtrn.reset(factory->fromGrid(Grid)), rtrn != nullptr)
break;
return rtrn;
}
};
/**
* Implements AbstractBeamformFactory for a specific type of beamformer
*
* Create a global variable of this type in order to add it to the global
* BeamformRegistry
*/
template<class BeamformImplementation>
class BeamformFactory: public AbstractBeamformFactory
{
bool registered;
public:
explicit BeamformFactory(bool registerGlobal=true)
: registered(registerGlobal)
{
/* don't move this to the base class to avoid issues around
* half-initialized objects in the registry
*/
if(registerGlobal)
BeamformRegistry::globalInstance().add(this);
}
virtual ~BeamformFactory()
{
if(registered)
BeamformRegistry::globalInstance().remove(this);
}
virtual Beamform* fromGrid(matlab::data::StructArray& Grid) override
{ return BeamformImplementation::fromGrid(Grid); }
};
/* in CPP files */
BeamformRegistry& BeamformRegistry::globalInstance()
{
static BeamformRegistry instance;
return instance;
}
/*
* Make global variables to add entries to registry.
* These can be scattered across different cpp files
*/
BeamformFactory<bfmPlaneWave> planeWaveFactoryInstance;
BeamformFactory<bfmXRflMTX> XRflMTXFactoryInstance;
现在您可以简单地调用 BeamformRegistry::globalInstance().beamformForGrid(Grid)
来访问所有已注册的波束形成实现并扩展实现的数量,您只需将工厂实例分散到您的 cpp 文件中。
我不确定的一件事是它如何与 MEX 交互。 Matlab 什么时候加载它的扩展?如果这只是以某种形式的惰性方式发生,那么全局构造函数可能不会足够快地执行。我想用一些打印语句来检查是值得的。