【发布时间】:2012-06-06 10:53:28
【问题描述】:
我正在尝试构建一个 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 来放弃泛型。然后,所有包含通配符的方法都将使用擦除类型编译,而其他方法可以保留完整的模板类型。
您如何看待这些方法?您在哪里看到它们的缺点/优点? 对于如何尽可能干净地实现通配符,同时在代码中保留尽可能多的通用信息,您还有其他想法吗?
【问题讨论】:
-
" 虽然它们是完全不同的东西(元编程与仅编译时类型增强)," C++ 模板两者都提供。
-
是否有效取决于
Collection的实现。如果它有任何虚函数,我怀疑它是从 Java 翻译过来的,它有未定义的行为(这不应该被认为是暗示如果没有虚函数就可以了)。正如我在另一个问题中提到的那样,reinterpret_cast并不是非常有用。 -
为什么?当然,它有虚方法。但是,如果它们共享相同的内存布局,则将
Collection<Object*>的方法调用到实际上是Collection<String*>的对象上应该没问题。 -
首先,因为
reinterpret_cast的结果是未指定的。其次,因为它会导致违反别名规则,导致未定义的行为。
标签: java c++ templates generics compiler-construction