5

我正在尝试构建一个 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 来放弃泛型。然后,所有包含通配符的方法都将使用擦除类型编译,而其他方法可以保留完整的模板类型。

您如何看待这些方法?您在哪里看到它们的缺点/优点?对于如何尽可能干净地实现通配符,同时在代码中保留尽可能多的通用信息,您是否还有其他想法?

4

3 回答 3

6

不考虑垃圾收集,语言很熟悉,所以整个过程已经很好了。

不。虽然这两种语言实际上看起来很相似,但它们在“如何完成”方面却大不相同您尝试的这种 1:1 反编译将导致糟糕的、性能不佳的,并且很可能是错误的 C++ 代码,特别是如果您不是在查看独立应用程序,而是在手动查看可能与“正常”接口的东西时- 编写的 C++。

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

请注意有多少早期的 Java“基准”声称 Java 比 C++ 更快,因为 Java 传播者采用 Java 代码并将其 1:1 转换为 C++,就像您计划做的那样。这种转译没有什么可取胜的。

于 2012-06-06T11:18:44.247 回答
5

您尚未讨论的一种方法是使用包装类模板处理通用通配符。因此,当您Collection<? extends T>看到. 然后你在这个包装器(和其他类似的包装器)中进行类型擦除,这意味着生成的 C++ 处理起来相当不错。Collection<T>Collection<?>

reinterpret_cast您的电锯不能保证正常工作。例如,如果在 中存在多重继承String,那么通常甚至不可能将 a 类型双关语String*作为 a Object*,因为从String*to的转换Object*可能涉及对地址应用偏移量(不仅如此,使用虚拟基类)[**] . 我希望您将在 C++-from-Java 代码中对接口使用多重继承。好的,所以它们将没有数据成员,但它们将具有虚函数,并且 C++ 对您想要的东西没有特别的考虑。我认为使用标准布局类,您可能可以重新解释指针本身,但是(a)这对您来说太强了,并且(b)它仍然不意味着您可以重新解释集合。

[*] 管他呢。我忘记了通配符在 Java 中如何工作的详细信息,但是当您尝试将 a 添加T到 a时会发生什么List<? extends T>T结果不是 的实例?,请这样做:-) 棘手的部分是自动生成任何给定的泛型类或接口的包装器。

[**] 而且因为严格的别名禁止它。

于 2012-06-06T11:37:57.583 回答
4

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

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

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

于 2012-06-06T11:48:04.923 回答