首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++ N-API多类型签名

C++ N-API多类型签名
EN

Stack Overflow用户
提问于 2021-11-26 23:03:24
回答 2查看 198关注 0票数 1

我正在学习C++,和OpenCVnode-addon-api一起玩。我想为cv::Vec创建自己的包装器。文档

代码语言:javascript
复制
#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>会工作,并创建一个三维向量。

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

我应该创建类Vec2Vec3等等来扩展当前的Vec类吗?

当我看到更有经验的开发人员如何处理这个问题时,我在opencv4nodejs中找到了这个例子。这似乎更合理:

  • 1个cpp文件
  • 基头文件
  • 类变体的附加头文件

我有完整的例子在GitHub上

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-12-02 02:42:47

既然你提到你在学习C++,我试着给出一些额外的解释,而不仅仅是放弃一个答案,假设你知道我在说什么。任何问题或澄清都让我知道。

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

您不需要重载构造函数。传递给构造函数的参数直到运行时才知道,因此它们不能用于填充所需的模板参数,这些参数必须在cv::Vec类的编译时设置。

简历:Vec的doc中我们可以看到cv::Vec本身是一个模板类。

代码语言:javascript
复制
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++的一个基本原则:“你不使用的东西,你不付[BS94]的钱。”

您可能已经注意到,您所链接的代码似乎没有提供这些参数,而是使用了类似于cv::Vec6d的东西。这是因为它们有一些更常见的预定义组合,如类型化名。。使用using关键字而不是typedef是定义这些别名的更现代的惯用方法。

坚持纯粹的C++决策片刻,您有两种方式向前推进这一理解。您可以为您希望支持的_Tpcn的每个组合定义一个新类,就像您所链接的opencv4nodejs代码所做的那样,也可以模板您的Vec类。

代码语言:javascript
复制
// with templates
template< class ElementType, int Count >
class Vec {
private:
  cv::Vec<ElementType, Count> *_wrappedClass_;
};

这将允许您在编写代码一次时,通过实例化类似于Vec<double, 5> myVec;的东西来构造您自己的任意类型和大小的Vec类,这将导致您的类具有一个私有成员_wrappedClass_,该成员是指向类型cv::Vec<double, 5>的指针。

不幸的是,一旦我们将node-addon-api需求带回来,我们就不得不考虑进一步的复杂问题。

我应该创建类Vec2、Vec3等等来扩展当前的Vec类吗?

也许吧。来自Napi ObjectWrap文档

在初始化时,必须使用Napi::ObjectWrap::DefineClass()方法来连接访问器和方法回调。它接受一个属性描述符列表,它可以通过基类上的各种静态方法来构造。

然后查看DefineClass的docs它所做的不仅仅是链接访问器和方法回调,它通过第二个参数在Javascript运行时为您的对象提供了一个名称。

在utf8name中:以空结尾的字符串,表示JavaScript构造函数的名称。

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

名称从根本上说是该类型的一部分,但您也有选择。您可以再次定义许多Vec类型,也可以通过类似于Count的模板参数传递。

然而,我有一种感觉,当你深入到这个问题的时候,你会遇到更多的东西来充分定义这种类型。因此,与其缓慢地扩展模板参数的数量,我们还可以认识到这些参数的存在都是为了对类的行为进行小的调整。我们可以在C++中使用一个叫做策略类的成语。通过这种方式,我们可以将指定给基本类的部分提取到它们自己的对象中,同时将基本类保留为代码的一个副本。

例如,我们可能有:

代码语言:javascript
复制
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;
};

我们只需将类实例化为:

代码语言:javascript
复制
Vec<Vec2dPolicy> my2dvec;
Vec<Vec3dPolicy> my3dvec;

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

那么,您的Vec类是什么样的呢?最终看起来会是这样的:

代码语言:javascript
复制
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_构造函数的正确类型,它也将无法编译。

这是使用一个参数包和元函数如果,它利用SFINAE来确保Vec(...)只在参数数与Policy::Count匹配时作为代码发出。

请注意,cv::Vec并不为每个Count定义构造函数。它们只为0-10和14定义它们。在这个设置中,类将定义11,但当将11传递给_wrappedClass_时它将失败,因为它没有编译。

因此,现在您有了相同的功能,就像为每个维度和底层类型组合编写了一堆Vec2d Vec3d Vec4d等类一样,但您只需要编写一次核心代码。

如果有什么不清楚的话请告诉我。

票数 1
EN

Stack Overflow用户

发布于 2021-12-05 17:38:39

所以,通过@thomasMouton在他接受的问题中所写的内容,我把类似的东西组合在一起。

我们可以制定政策。

代码语言:javascript
复制
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;
};

然后我们开始上课,就像平时一样。

代码语言:javascript
复制
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  Vec<Vec2dPolicy>::Init(env, exports);
  Vec<Vec3dPolicy>::Init(env, exports);

  return exports;
}

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

代码语言:javascript
复制
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);
};

然后是实际实施:

代码语言:javascript
复制
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;

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70130805

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档