1

我正在学习 C++ 并玩弄OpenCVand node-addon-api。我想为cv::Vec. 文档

#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希望我告诉typeand size。所以像这样:cv::Vec<int, 3>可以工作并创建一个 3 维向量。

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

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

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

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

在 GitHub 上有完整的示例。

4

2 回答 2

1

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

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

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

cv::Vec的文档中,我们可以看到 cv::Vec 本身就是一个模板类。

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

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

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

这会导致您的编译错误“命名空间'cv'中没有名为'Vec'的类型”正在发出,因为确实没有没有cv::Vec任何模板参数的 a 这样的东西。cv::Vec<int,5>如果你实例化一个,可能会有一个。编译器不会为您以后不再使用的模板生成类型。编译器将遵循 C++ 的一个基本原则:“你不使用的东西,你不需要为 [BS94] 付费”

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

暂时坚持纯粹的 C++ 决策,你有两种方式来理解这种理解。您可以为您希望支持的每个组合定义一个新类,因为您链接的 opencv4nodejs 代码已经完成,或者您可以模板您的_Tp类。cnVec

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

这将允许您在编写代码时使用构建您自己的任何类型和大小的 Vec 类,方法是通过实例化诸如Vec<double, 5> myVec;这将导致您的类具有_wrappedClass_一个指向 type 的指针的私有成员cv::Vec<double, 5>

不幸的是,一旦我们node-addon-api重新提出要求,我们就不得不考虑进一步的复杂性。

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

也许。来自Napi ObjectWrap 文档

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

然后查看DefineClass的文档,它不仅仅链接访问器和方法回调,它还通过第二个参数在 Javascript 运行时中为您的对象命名。

[in] utf8name:以 Null 结尾的字符串,表示 JavaScript 构造函数的名称。

在我们上面的示例中,对于ElementType和的每个组合,这将需要一个不同的值。Count该名称不应在 aVec<double, 5>和之间共享,Vec<double, 3>因为即使Napi没有引发错误,您也不知道您在 Javascript 中得到的是哪个名称。

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

但是,我有一种感觉,当您进一步研究时,您将遇到完全定义此类型的其他内容。因此,与其慢慢扩大模板参数的数量,我们可以意识到这些参数都存在,以便对类的行为进行微调。我们可以在 C++ 中使用一个叫做policy classes的习语。通过这种方式,我们可以将指示基础类的部分提取到它们自己的对象中,同时为基础类保留一个代码副本。

例如我们可能有:

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;
};

目标是我们只需要将您的类实例化为:

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

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

那么你的Vec班级是什么样的呢?最终它看起来像:

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_' 构造函数的正确类型,它也将无法编译。

这是使用参数包和元函数enable_if,它利用SFINAE确保Vec(...)仅在参数数量与Policy::Count.

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

所以现在你拥有的功能就像你为每个维度和底层类型的组合编写了一堆 Vec2d Vec3d Vec4d 等类一样,除了你只需要编写一次核心代码。

让我知道是否有任何不清楚的地方。

于 2021-12-02T02:42:47.540 回答
0

因此,根据@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;

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

于 2021-12-05T17:38:39.757 回答