我正在尝试构建一个Java到C ++的转换器(即Java代码进入,语义上“等效”(或多或少)C ++代码出来)。
不考虑垃圾收集,语言非常熟悉,因此整个过程已经很好用了。然而,一个问题是C ++中不存在的泛型。当然,最简单的方法是执行java编译器所做的擦除。但是,生成的C ++代码应该很好处理,所以如果我不丢失泛型类型信息会很好,即如果C ++代码仍然可以使用List<X>
代替{{{}},那将会很好。 1}}。否则,C ++代码需要在使用此类泛型的任何地方进行显式转换。这很容易出错并且不方便。
所以,我试图找到一种方法以某种方式获得更好的泛型代表。当然,模板似乎是一个很好的候选人。尽管它们是完全不同的(元编程与仅编译时类型增强),但它们仍然有用。只要不使用通配符,只需将通用类编译为模板即可。然而,只要通配符发挥作用,事情就会变得非常混乱。
例如,请考虑以下列表的java构造函数:
List
如何编译?我有想法在模板之间使用电锯重新解释。然后,上面的例子可以这样编译:
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来删除通用性。然后,所有包含通配符的方法都将使用已擦除类型进行编译,而其他方法可以保留完整模板类型。
您如何看待这些方法?你在哪里看到他们的优势? 您是否有任何其他想法可以尽可能清晰地实现通配符,同时尽可能在代码中保留通用信息?
答案 0 :(得分:6)
不考虑垃圾收集,语言非常熟悉,所以整个过程已经很好用了。
没有。虽然这两种语言实际上看起来非常相似,但它们与“事情如何完成”的显着显着不同。你正在尝试的这种1:1反式编译将导致可怕的,表现不佳的,并且很可能是错误的C ++代码,如果你不是在寻找一个独立的应用程序,那么尤其是接口“正常”,手动编写的C ++。
C ++需要与Java完全不同的编程样式。这开始于 not 让所有类型都派生自Object
,除非绝对必要,否则触及避免new
(然后尽可能地将其限制为构造函数,并使用相应的{{析构函数中的1}} - 或者更好的是,遵循下面的Potatoswatter建议,并且不会以“模式”结束,例如使容器符合STL并将delete
- 和begin
- 迭代器传递给另一个容器的构造函数而不是整个容器。我也没有在代码中看到const-correctness或pass-by-reference语义。
请注意有多少早期Java“基准”声称Java比C ++更快,因为Java传播者使用Java代码并将其转换为C ++ 1:1,就像您计划要做的那样。这种反编译无法赢得任何东西。
答案 1 :(得分:5)
您尚未讨论的方法是使用包装类模板处理通用通配符。因此,当您看到Collection<? extends T>
时,将其替换为模板的实例化,该模板实现了Collection<T>
之类的只读[*]接口,但包装了Collection<?>
的实例。然后你在这个包装器(和其他类似的包装器)中进行类型擦除,这意味着生成的C ++相当不错。
您的电锯reinterpret_cast
不保证可以正常工作。例如,如果String
中存在多重继承,则通常不可能将String*
作为Object*
进行输入,因为从String*
转换为{{ 1}}可能涉及向地址应用偏移量(超过该值,使用虚拟基类)[**]。我希望你在C ++中使用多重继承 - 从Java代码到接口。好的,所以他们没有数据成员,但他们将拥有虚拟功能,而C ++没有特别允许你想要的东西。我认为对于标准布局类,你可能会重新解释指针本身,但是(a)这对你来说太强大了,而且(b)它仍然不意味着你可以重新解释这个集合。
[*]或者其他什么。我忘记了通配符在Java中如何工作的细节,但是当你尝试将Object*
添加到T
时应该发生什么,而List<? extends T>
结果不是实例T
,执行:-)棘手的部分是为任何给定的泛型类或接口自动生成包装器。
[**]因为严格的别名禁止它。
答案 2 :(得分:4)
如果目标是用C ++表示Java语义,那么以最直接的方式表达。不要使用reinterpret_cast
,因为它的目的是击败 C ++的本地语义。 (在高级类型之间这样做几乎总是会导致程序崩溃。)
您应该使用引用计数或类似的机制,例如自定义垃圾收集器(尽管在这种情况下听起来不太可能)。因此,无论如何这些对象都会进入堆。
将通用List
对象放在堆上,并使用单独的类将其作为List<String>
或其他类进行访问。这样,持久对象具有泛型类型,可以处理Java可以表达的任何不正确的访问方式。访问器类只包含一个指针,您已经拥有该指针用于引用计数(即它是“本机”引用的子类,而不是堆的Object),并公开适当的下载接口。您甚至可以使用泛型源代码为访问者生成模板。如果确实想要尝试。