C++ N-API 多类型签名

C++ N-API Multiple Type Signatures

我正在学习 C++ 并使用 OpenCVnode-addon-api。我想为 cv::Vec 创建自己的包装器。 docs

#include <napi.h>
#include <opencv2/core/matx.hpp>

class Vec : public Napi::ObjectWrap<Vec> {
public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);

  explicit Vec(const Napi::CallbackInfo &info);

private:
  static Napi::FunctionReference constructor;

  //
  // no type named 'Vec' in namespace 'cv';
  // I would like this to be cv::Vec2 or cv::Vec3 ... of any type
  cv::Vec *_wrappedClass_;

  // duplicate member '_wrappedClass_'
  // cv::Vec2 *_wrappedClass_;
  // cv::Vec3 *_wrappedClass_;
};

当然上面的例子是行不通的,因为cv::Vec希望我告诉typesize。所以这样的事情:cv::Vec<int, 3> 会工作并创建一个 3 维向量。

我的问题是如何正确重载构造函数并定义 _wrappedClass_ 类型?

我是否应该创建 classes Vec2Vec3 等等,以扩展当前的 Vec class?

当我查看更有经验的开发人员如何处理问题时,我在 opencv4nodejs 中找到了这个示例。好像比较合理:

我有完整的例子on GitHub here

既然您已经提到您正在学习 C++,那么除了抛出一个答案并假设您知道我在说什么之外,我还尝试对其他事情进行一些额外的解释。如有任何问题或说明,请告诉我。

My question is how do I properly overload the constructor and define the wrappedClass type?

您没有重载构造函数。传递给构造函数的参数直到 运行 时间才知道,因此它们不能用于填充必须在编译时在 cv::Vec class 中设置的模板参数。 =58=]

cv::Vec 的文档中我们可以看到 cv::Vec 本身就是一个模板化的 class.

template<typename _Tp, int cn>
class cv::Vec< _Tp, cn >

这意味着要实例化一个 cv::Vec 您必须提供这两个模板参数。

C++ 是强类型的,模板参数是类型的一部分。这意味着 cv::Vec<int, 5> 不是 cv::Vec<int, 4>cv::Vec<double, 5> 相同的类型。与 C++ 中的任何其他类型一样,模板必须是可推导的、完全形成的并在编译时设置。

这会导致您的编译错误“名称空间 'cv' 中没有名为 'Vec' 的类型”,这是因为确实没有 cv::Vec 之类的东西而没有任何模板争论。如果您实例化一个,可能会有一个 cv::Vec<int,5>。编译器不会为您以后不再使用的模板生成类型。编译器将遵循 C++ 的基本原则:"What you don’t use, you don’t pay for [BS94]".

您可能已经注意到您 link 编写的代码似乎没有提供这些参数,而是使用了类似 cv::Vec6d 的内容。这是因为他们有pre-defined a few more common combinations as type aliases.。使用 using 关键字而不是 typedef 是定义这些别名的更现代的惯用方法。

暂时坚持使用纯 C++ 进行决策,您可以通过两种方式推进这种理解。您可以为您希望支持的 _Tpcn 的每个组合定义一个新的 class,因为 opencv4nodejs 代码已经完成了您 linked,或者您可以模板化您的Vec class.

// with templates
template< class ElementType, int Count >
class Vec {
private:
  cv::Vec<ElementType, Count> *_wrappedClass_;
};

这将允许您在编写代码时通过实例化 Vec<double, 5> myVec; 之类的东西来构建您自己的任何类型和大小的 Vec classes,这将导致您的 class 与私有成员 _wrappedClass_ 是指向类型 cv::Vec<double, 5>.

的指针

不幸的是,一旦我们恢复 node-addon-api 要求,我们就不得不考虑进一步的并发症。

Should I create classes Vec2, Vec3, and so on that would extend the current Vec class?

也许吧。来自 Napi ObjectWrap docs:

At initialization time, the Napi::ObjectWrap::DefineClass() method must be used to hook up the accessor and method callbacks. It takes a list of property descriptors, which can be constructed via the various static methods on the base class.

然后查看 DefineClass 的文档,它所做的不仅仅是 link 访问器和方法回调,它还为您的对象在 Javascript 运行时间通过第二个参数。

[in] utf8name: Null-terminated string that represents the name of the JavaScript constructor function.

在我们上面的示例中,ElementTypeCount 的每个组合都需要不同的值。该名称不应在 Vec<double, 5>Vec<double, 3> 之间共享,因为即使 Napi 没有抛出错误,您也不知道 Javascript.

名称基本上是类型的一部分,但您也有选择权。您可以重新定义许多 Vec 类型,或者您可以通过类似于 Count 的模板参数传递它。

但是,我有一种感觉,当您进一步研究这个问题时,您将 运行 进入完全定义此类型的其他内容。因此,与其慢慢地扩展模板参数的数量,我们可以意识到这些参数的存在都是为了对 class 的行为进行微小的调整。我们可以使用 C++ 中的成语 policy classes。通过这种方式,我们可以将决定基础 class 的部分提取到它们自己的对象中,同时留下基础 class 代码的单个副本。

例如我们可能有:

struct Vec2dPolicy {
  constexpr static char name[] = "Vec2d";
  constexpr static int  Count  = 2;
  using ElementType = double;
};

struct Vec3dPolicy {
  constexpr static char name[] = "Vec3d";
  constexpr static int  Count  = 3;
  using ElementType = double;
};

我们只需要将您的 class 实例化为:

Vec<Vec2dPolicy> my2dvec;
Vec<Vec3dPolicy> my3dvec;

并拥有您需要的所有完全定义的 Vec 变体。

那么你的 Vec class 长什么样?最终它看起来像:

template< class Policy >
class Vec : public Napi::ObjectWrap< Vec< Policy > > {
public:
  Vec() {}

  template<class...Args, typename std::enable_if<sizeof...(Args) == Policy::Count, int>::type = 0>
  Vec(Args... args)
  : _wrappedClass_(args...)
  { }

  Napi::Object Init(Napi::Env env, Napi::Object exports) {
    Napi::Function func = DefineClass(
                            env,
                            Policy::name,
                            {
                            /* fill me in */
                            }
                          );
    /* fill me in */
  }

  // other member functions to fully define Napi object

private:
  cv::Vec<typename Policy::ElementType, Policy::Count> _wrappedClass_ = {};
};

请注意,我取出了指向您的私有成员的指针 _wrappedClass_,因为我不明白为什么那会是一个指针。

为了好玩而扔在那里是一种为 Vec 定义构造函数的方法,它接受 Policy::Count 个参数并将它们传递给 _wrappedClass_ 的构造函数。如果他们传递了错误数量的参数,它将无法编译,因为没有定义构造函数。如果它们传入的参数类型无法转换为 _wrappedClass_ 构造函数的正确类型,它也将无法编译。

这是使用 parameter pack along with the metafunction enable_if which takes advantage of SFINAE 来确保 Vec(...) 仅在参数数量与 Policy::Count 匹配时作为代码发出。

请注意 cv::Vec 并未为每个 Count 定义构造函数。他们只为 0-10 和 14 定义它们。使用此设置,您的 class 将定义 11,但在将 11 传递给 _wrappedClass_ 时它将无法编译,因为它没有。

因此,现在您拥有的功能与为每个维度和基础类型的组合编写了一堆 Vec2d Vec3d Vec4d 等等 classes 相同,只是您只需编写核心代码一次。

如果有任何不清楚的地方,请告诉我。

根据@thomasMouton 在他接受的问题中所写的内容,我整理了类似这样的内容。

我们可以定义策略。

struct Vec2dPolicy {
  constexpr static char *name = "Vec2";
  constexpr static int  Count  = 2;
  using ElementType = double;
};

struct Vec3dPolicy {
  constexpr static char *name = "Vec3";
  constexpr static int  Count  = 3;
  using ElementType = double;
};

然后我们像往常一样启动 类。

Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  Vec<Vec2dPolicy>::Init(env, exports);
  Vec<Vec3dPolicy>::Init(env, exports);

  return exports;
}

我们变体的头文件可能如下所示:

template<class VariantPolicy>
class Vec : public Napi::ObjectWrap<Vec<VariantPolicy>> {
public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);

  explicit Vec(const Napi::CallbackInfo &info);

private:
  static Napi::FunctionReference constructor;

  Napi::Value getX(const Napi::CallbackInfo &info);
  Napi::Value getY(const Napi::CallbackInfo &info);
  Napi::Value getZ(const Napi::CallbackInfo &info);
};

然后是实际执行:

template<class VariantPolicy>
Napi::Value Vec<VariantPolicy>::getZ(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  return Napi::Number::New(env, 3);
}

template<class VariantPolicy>
Napi::Value Vec<VariantPolicy>::getY(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  return Napi::Number::New(env, 2);
}

template<class VariantPolicy>
Napi::Value Vec<VariantPolicy>::getX(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  return Napi::Number::New(env, 1);
}

template<class VariantPolicy>
Vec<VariantPolicy>::Vec(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Vec<VariantPolicy>>(info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);
}

template<class VariantPolicy>
Napi::Object Vec<VariantPolicy>::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = Napi::ObjectWrap<Vec<VariantPolicy>>::DefineClass(env, VariantPolicy::name, {
      Napi::ObjectWrap<Vec<VariantPolicy>>::InstanceAccessor("x", &Vec<VariantPolicy>::getX, nullptr),
      Napi::ObjectWrap<Vec<VariantPolicy>>::InstanceAccessor("y", &Vec<VariantPolicy>::getY, nullptr),
      Napi::ObjectWrap<Vec<VariantPolicy>>::InstanceAccessor("z", &Vec<VariantPolicy>::getZ, nullptr),
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set(VariantPolicy::name, func);
  return exports;
}

template<class VariantPolicy>
Napi::FunctionReference Vec<VariantPolicy>::constructor;

这当然是简化版,但它回答了问题。