C++ N-API 多类型签名
C++ N-API Multiple Type Signatures
我正在学习 C++ 并使用 OpenCV
和 node-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
希望我告诉type
和size
。所以这样的事情:cv::Vec<int, 3>
会工作并创建一个 3 维向量。
我的问题是如何正确重载构造函数并定义 _wrappedClass_
类型?
我是否应该创建 classes Vec2
、Vec3
等等,以扩展当前的 Vec
class?
当我查看更有经验的开发人员如何处理问题时,我在 opencv4nodejs 中找到了这个示例。好像比较合理:
- 1 个 cpp 文件
- 基础头文件
- class 变体的附加头文件
我有完整的例子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++ 进行决策,您可以通过两种方式推进这种理解。您可以为您希望支持的 _Tp
和 cn
的每个组合定义一个新的 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.
在我们上面的示例中,ElementType
和 Count
的每个组合都需要不同的值。该名称不应在 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;
这当然是简化版,但它回答了问题。
我正在学习 C++ 并使用 OpenCV
和 node-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
希望我告诉type
和size
。所以这样的事情:cv::Vec<int, 3>
会工作并创建一个 3 维向量。
我的问题是如何正确重载构造函数并定义 _wrappedClass_
类型?
我是否应该创建 classes Vec2
、Vec3
等等,以扩展当前的 Vec
class?
当我查看更有经验的开发人员如何处理问题时,我在 opencv4nodejs 中找到了这个示例。好像比较合理:
- 1 个 cpp 文件
- 基础头文件
- class 变体的附加头文件
我有完整的例子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++ 进行决策,您可以通过两种方式推进这种理解。您可以为您希望支持的 _Tp
和 cn
的每个组合定义一个新的 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.
在我们上面的示例中,ElementType
和 Count
的每个组合都需要不同的值。该名称不应在 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;
这当然是简化版,但它回答了问题。