如何使用来自 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 什么时候加载它的扩展?如果这只是以某种形式的惰性方式发生,那么全局构造函数可能不会足够快地执行。我想用一些打印语句来检查是值得的。