使用通配符将Java泛型编译为C++模板

gex*_*ide 5 c++ java compiler-construction generics templates

我正在尝试构建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);
Run Code Online (Sandbox Code Playgroud)

怎么编译呢?我有想法在模板之间使用电锯重新解释.然后,上面的例子可以这样编译:

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));
Run Code Online (Sandbox Code Playgroud)

然而,问题是这种重新解释演员是否会产生预期的行为.当然,它很脏.但它会起作用吗?通常,List<Object*>并且List<String*>应该具有相同的内存布局,因为它们的模板参数只是一个指针.但这有保证吗?

我想到的另一个解决方案是通过模板方法替换使用通配符的方法,这些方法实例化每个通配符参数,即将构造函数编译为

template<class T>
class List{

template<class S>
List(Collection<S*> c){
    this.addAll(c);
}
}
Run Code Online (Sandbox Code Playgroud)

当然,所有其他涉及通配符的方法addAll都需要模板参数.这种方法的另一个问题是例如在类字段中处理通配符.我不能在这里使用模板.

第三种方法是混合方法:将泛型类编译为模板类(调用它T<X>)和擦除类(调用它E).模板类T<X>继承E自已擦除的类,因此始终可以通过向上转换为E来删除通用性.然后,所有包含通配符的方法都将使用擦除类型进行编译,而其他方法可以保留完整的模板类型.

你怎么看待这些方法?你在哪里看到他们的优势?您是否有任何其他想法可以尽可能地将通配符实现为尽可能干净,同时尽可能在代码中保留通用信息?

Dev*_*lar 6

不考虑垃圾收集,语言非常熟悉,因此整个过程已经很好用了.

虽然这两种语言实际上看起来很相似,但它们与"如何完成" 有很大不同.当你尝试这样的1:1反编译将导致可怕的,表现不佳的,并且很可能是错误的C++代码,特别是如果你不是在寻找一个独立的应用程序,而是在可能与"正常"接口的东西上,手动写的C++.

C++需要与Java 完全不同的编程风格.这开始于没有所有类型派生Object,new除非绝对必要,否则触及避免(然后尽可能地将其限制为构造函数,delete在析构函数中使用相应的 - 或者更好,遵循下面的Potatoswatter的建议),并且不会结束在"模式"中,比如使容器符合STL标准,并将begin- 和 - 运行end符传递给另一个容器的构造函数而不是整个容器.我也没有在代码中看到const-correctness或pass-by-reference语义.

请注意有多少早期Java"基准"声称Java比C++更快,因为Java传播者使用Java代码并将其转换为C++ 1:1,就像您计划要做的那样.这种反编译没有什么可以赢得的.


Ste*_*sop 5

您尚未讨论的方法是使用包装类模板处理通用通配符.因此,当您看到时Collection<? extends T>,将其替换为模板的实例化,该模板实例化了一个只读的[*]接口,Collection<T>但包含了一个实例Collection<?>.然后你在这个包装器(和其他类似的包装器)中进行类型擦除,这意味着生成的C++相当不错.

您的电锯reinterpret_cast无法保证工作.例如,如果存在多个继承String,那么通常甚至不可能将-s-a表示String*为一个Object*,因为转换String*Object*可能涉及将偏移量应用于地址(更多,使用虚拟基类)[**] .我希望你在C++中使用多重继承 - 从Java代码到接口.好的,所以他们没有数据成员,但他们将拥有虚拟功能,而C++没有特别允许你想要的东西.我认为使用标准布局类你可能会重新解释指针本身,但是(a)这对你来说太强大了,(b)它仍然不意味着你可以重新解释这个集合.

[*] 管他呢.我忘了的通配符在Java中是如何工作的细节,但无论是应该当您尝试将添加到发生TList<? extends T>,和T原来不被实例?,这样做产生自动:-)棘手的部分是任何给定泛型类或接口的包装器.

[**]因为严格的别名禁止它.


Pot*_*ter 4

如果目标是用 C++ 表示 Java 语义,那么就以最直接的方式实现。不要使用reinterpret_cast,因为它的目的是破坏C++ 的本机语义。(在高级类型之间这样做几乎总是会导致程序崩溃。)

您应该使用引用计数或类似的机制,例如自定义垃圾收集器(尽管在这种情况下听起来不太可能)。所以这些对象无论如何都会进入堆。

将通用List对象放在堆上,并使用单独的类将其作为 aList<String>或其他内容进行访问。这样,持久对象就具有通用类型,可以处理 Java 可以表达的任何格式错误的访问方式。访问器类只包含一个指针,您已经拥有该指针用于引用计数(即,它是“本机”引用的子类,而不是堆的对象),并公开适当的向下转换的接口。您甚至可以使用泛型源代码为访问器生成模板。如果你真的想尝试的话。