C++如何避免模板类型展开导致的成员函数指针强制转换
问题根因
你遇到的编译报错本质是成员函数指针签名不匹配,代码里用到的JNIEnv::CallIntMethod本身是可变参数函数,原生签名是:
jint (JNIEnv::*)(jobject, jmethodID, ...)
而你PrimitiveAccessor构造函数接收的成员函数指针类型是没有可变参数后缀的Type (JNIEnv::*)(jobject, jmethodID),两者属于完全不同的类型,所以编译器会报类型不匹配错误,你之前的强制转换属于不安全的隐式行为,运行时传参如果出错会直接崩溃。
无强制转换的解决方案
下面给出几种常用的无转换实现方案:
方案1:修改成员函数指针定义适配可变参数签名
直接把类内成员函数指针和构造函数参数的签名,加上可变参数后缀即可,这是改动最小的方案:
template<class Type> class PrimitiveAccessor { public : PrimitiveAccessor( JNIEnv* env, const char name[], const char ctorSig[], // 增加...匹配JNI原生可变参数签名 Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID, ...) ) { this->env = env; this->type = (jclass)env->NewGlobalRef(env->FindClass(name)); this->callTypeMethodFunction = callTypeMethodFunction; // 补充构造方法ID获取逻辑 this->constructorId = env->GetMethodID(this->type, "<init>", ctorSig); } ~PrimitiveAccessor(){ env->DeleteGlobalRef(this->type); } private: JNIEnv* env; jclass type; jmethodID constructorId; jmethodID callTypeMethodId; // 同样增加...后缀 Type (JNIEnv::*callTypeMethodFunction) (jobject, jmethodID, ...); }; // 此时init方法不需要任何强转即可编译通过 void Environment::init() { jintAccessor = new PrimitiveAccessor<jint>( env, "java/lang/Integer", "(I)V", &JNIEnv::CallIntMethod ); }
方案2:使用非可变参数的JNI方法版本
如果你不想在自己的代码里用可变参数,可以选择JNI提供的固定参数版本的调用方法:
Call<Type>MethodV:第三个参数为va_list类型Call<Type>MethodA:第三个参数为jvalue*类型
你只需要调整类的传参逻辑适配这两个固定签名的方法即可,完全不需要做任何类型转换,同时类型安全性比可变参数更高。
方案3:用std::function做类型擦除(更灵活)
如果后续需要扩展更多不同签名的调用逻辑,可以用std::function对调用逻辑做封装,完全不用关心成员函数指针的类型匹配问题:
#include <functional> template<class Type> class PrimitiveAccessor { public : PrimitiveAccessor( JNIEnv* env, const char name[], const char ctorSig[], std::function<Type(jobject, jmethodID)> callFunc ) : env(env), callFunc(std::move(callFunc)) { type = (jclass)env->NewGlobalRef(env->FindClass(name)); constructorId = env->GetMethodID(type, "<init>", ctorSig); } // 其他逻辑保持不变 private: JNIEnv* env; jclass type; jmethodID constructorId; std::function<Type(jobject, jmethodID)> callFunc; }; // 初始化时用lambda包装调用即可 void Environment::init() { jintAccessor = new PrimitiveAccessor<jint>( env, "java/lang/Integer", "(I)V", [env](jobject obj, jmethodID mid) -> jint { return env->CallIntMethod(obj, mid); } ); }
内容的提问来源于stack exchange,提问作者JJB
相关产品推荐
相关产品推荐

