【问题标题】:How can I prevent template type expansion in C++?如何防止 C++ 中的模板类型扩展?
【发布时间】:2021-11-04 03:42:12
【问题描述】:

我编写了以下使用成员指针函数的类:

#include <stdlib.h>
#include <vector>

template<class Type>
class PrimitiveAccessor {
    public :
        PrimitiveAccessor(
            JNIEnv* env, const char name[], const char ctorSig[],
            Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID)
        ) {
            this->env = env;
            this->type = (jclass)env->NewGlobalRef(env->FindClass(name));
            this->callTypeMethodFunction = callTypeMethodFunction;
        }
        ~PrimitiveAccessor(){
            env->DeleteGlobalRef(this->type);
        }

    private:
        JNIEnv* env;
        jclass type;
        jmethodID constructorId;
        jmethodID callTypeMethodId;
        Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID);
};

class Environment {
    public:
        Environment(JNIEnv* env) {
            this->env = env;
            this->init();
        }
        ~Environment(){
            this->env = 0;
            delete(this->jintAccessor);
            this->jintAccessor = 0;
        }
    private:
        JNIEnv* env;
        PrimitiveAccessor<jint>* jintAccessor;

        void init() {
            jintAccessor = new PrimitiveAccessor<jint>(
                env, "java/lang/Integer",
                "(I)V", &JNIEnv::CallIntMethod
            );
        }
};

但是在编译时出现以下编译错误:

F:\Shared\Workspaces\Projects\DriverFunctionSupplierNative.cpp: In member function 'void Environment::init()':
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:75:4: error: no matching function for call to 'PrimitiveAccessor<long int>::PrimitiveAccessor(JNIEnv*&, const char [18], const char [5], jint (JNIEnv_::*)(jobject, jmethodID, ...))'
    );
    ^
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:36:3: note: candidate: 'PrimitiveAccessor<Type>::PrimitiveAccessor(JNIEnv*, const char*, const char*, Type (JNIEnv_::*)(jobject, jmethodID)) [with Type = long int; JNIEnv = JNIEnv_; jobject = _jobject*; jmethodID = _jmethodID*]'
   PrimitiveAccessor(
   ^~~~~~~~~~~~~~~~~
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:36:3: note:   no known conversion for argument 4 from 'jint (JNIEnv_::*)(jobject, jmethodID, ...)' {aka 'long int (JNIEnv_::*)(_jobject*, _jmethodID*, ...)'} to 'long int (JNIEnv_::*)(jobject, jmethodID)' {aka 'long int (JNIEnv_::*)(_jobject*, _jmethodID*)'}
F:\Shared\Workspaces\Projects\JNIDriverFunctionSupplierNative.cpp:34:7: note: candidate: 'constexpr PrimitiveAccessor<long int>::PrimitiveAccessor(const PrimitiveAccessor<long int>&)'
 class PrimitiveAccessor {
       ^~~~~~~~~~~~~~~~~

我通过强制转换成员函数指针来临时修复它:

void init() {
    jintAccessor = new PrimitiveAccessor<jint>(
        env, "java/lang/Integer",
        "(I)V", (long (JNIEnv::*) (jobject, jmethodID))&JNIEnv::CallIntMethod
    );
}

我注意到传递给模板的类型被扩展了:有没有办法避免这种转换?

【问题讨论】:

  • 有多余的省略号(至于printf)...Type (JNIEnv::*) (jobject, jmethodID) != Type (JNIEnv::*) (jobject, jmethodID, ...)
  • 没有什么是“扩展的”。 jint 只是long int别名。这不是这里的问题(Jarod 的评论指出了实际问题)。
  • 旁白:虽然0 是一个空指针文字,但首选的是nullptr。并不是说您需要修改即将不复存在的指针。
  • 旁白2:为什么需要指向PrimitiveAccessor 的指针?在 C++ 中创建对象不需要new
  • @JJB 你有一个基本的误解:改变Environment 的成员类型并不会改变处理Environment 的方式,它是一个实现细节。而且我知道 JNI 是如何工作的,我自己也编写了非常相似的包装器。

标签: c++ templates java-native-interface template-classes


【解决方案1】:

你需要指定一个匹配的类型,而不是一个相似的类型。

我还清理了很多你的惯用 C++。不要使用new;这不是必需的。如果您的数据成员自行清理,则不需要用户定义的析构函数。您应该在成员初始化器列表中初始化您的数据成员,而不是在构造函数的主体中。对字符串使用std::string

#include <stdlib.h>
#include <vector>
#include <memory>
#include <string>

struct GlobalRefDeleter {
    JNIEnv* env;
    void operator()(jobject type) {
        env->DeleteGlobalRef(type);
    }
};

using JClass = std::unique_ptr<_jclass, GlobalRefDeleter>;

JClass getClass(JNIEnv* env, const std::string & name) {
    return { static_cast<jclass>(env->NewGlobalRef(env->FindClass(name.c_str()))), env };
}

template<class Type>
class PrimitiveAccessor {
    using CallMethod = Type (JNIEnv::*) (jobject, jmethodID, ...);
    public :
        PrimitiveAccessor(
            JNIEnv* env, const std::string & name, const std::string & ctorSig,
            CallMethod callMethod)
        ) : env(env), type(getClass(env, name)), callMethod(callMethod)
        {
        }

    private:
        JNIEnv* env;
        JClass type;
        jmethodID constructorId;
        jmethodID callTypeMethodId;
        CallMethod callMethod;
};


class Environment {
    public:
        Environment(JNIEnv* env) : env(env), jintAccessor(env, "java/lang/Integer", "(I)V", &JNIEnv::CallIntMethod)
        {
        }
    private:
        JNIEnv* env;
        PrimitiveAccessor<jint> jintAccessor;
};

【讨论】:

  • 很好的解决方案,但我必须删除JVM调用的JNI_Onunload函数上的环境
  • @JJB 这样吗?你仍然可以这样做。我没有改变Environment 的公开行为。隐式创建的析构函数与显式析构函数执行相同的清理
猜你喜欢
  • 1970-01-01
  • 2019-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多