我正在尝试构建一个 Java 到 C++ 的反编译器(即 Java 代码进入,语义上“等效”(或多或少)C++ 代码出来)。
不考虑垃圾收集,语言很熟悉,所以整个过程已经很好了。然而,一个问题是 C++ 中不存在的泛型。当然,最简单的方法是像 java 编译器那样执行擦除。但是,生成的 C++ 代码应该很好处理,所以如果我不会丢失泛型类型信息会很好,也就是说,如果 C++ 代码仍然可以使用List<X>
而不是List
. 否则,C++ 代码在使用此类泛型的任何地方都需要显式转换。这是容易出错且不方便的。
所以,我试图找到一种方法来以某种方式获得更好的泛型表示。当然,模板似乎是一个不错的选择。尽管它们是完全不同的东西(元编程与仅编译时类型增强),但它们仍然很有用。只要不使用通配符,只需将泛型类编译为模板就可以很好地工作。但是,一旦通配符开始发挥作用,事情就会变得非常混乱。
例如,考虑以下列表的 java 构造函数:
class List<T>{
List(Collection<? extends T> c){
this.addAll(c);
}
}
//Usage
Collection<String> c = ...;
List<Object> l = new List<Object>(c);
如何编译这个?我有在模板之间使用链锯重新解释演员表的想法。然后,上面的例子可以这样编译:
template<class T>
class List{
List(Collection<T*> c){
this.addAll(c);
}
}
//Usage
Collection<String*> c = ...;
List<Object*> l = new List<Object*>(reinterpret_cast<Collection<Object*>>(c));
然而,问题是这种重新解释演员是否会产生预期的行为。当然,它很脏。但它会起作用吗?通常,List<Object*>
并且List<String*>
应该具有相同的内存布局,因为它们的模板参数只是一个指针。但这能保证吗?
我想到的另一个解决方案是将使用通配符的方法替换为实例化每个通配符参数的模板方法,即将构造函数编译为
template<class T>
class List{
template<class S>
List(Collection<S*> c){
this.addAll(c);
}
}
当然,所有其他涉及通配符的方法,比如addAll
然后也需要模板参数。这种方法的另一个问题是处理类字段中的通配符。我不能在这里使用模板。
第三种方法是混合方法:将泛型类编译为模板类(称为它T<X>
)和一个擦除类(称为它E
)。模板类T<X>
继承自擦除类E
,因此总是可以通过向上转换为 E 来放弃泛型。然后,所有包含通配符的方法都将使用擦除类型编译,而其他方法可以保留完整的模板类型。
您如何看待这些方法?您在哪里看到它们的缺点/优点?对于如何尽可能干净地实现通配符,同时在代码中保留尽可能多的通用信息,您是否还有其他想法?