C++ A C++;协方差/覆盖/循环问题
我正在编写Java子集编译器的后端。后端编写C++代码。不过,有些假设的java代码,我不知道如何翻译成C++。 下面的代码中显示了一个示例问题。A由B扩展,B由C扩展,下面分别是三个头文件A.h、B.h和C.h:C++ A C++;协方差/覆盖/循环问题,c++,covariance,circular-dependency,overriding,C++,Covariance,Circular Dependency,Overriding,我正在编写Java子集编译器的后端。后端编写C++代码。不过,有些假设的java代码,我不知道如何翻译成C++。 下面的代码中显示了一个示例问题。A由B扩展,B由C扩展,下面分别是三个头文件A.h、B.h和C.h: #ifndef A_H #define A_H class B; class A { public: virtual B* get(); } #endif /* !defined(A_H) */ ========================== #ifndef B_H
#ifndef A_H
#define A_H
class B;
class A {
public: virtual B* get();
}
#endif /* !defined(A_H) */
==========================
#ifndef B_H
#define B_H
#include "A.h"
class C;
class B : public A {
public: virtual C* get();
}
#endif /* !defined(B_H) */
==========================
#ifndef C_H
#define C_H
#include "B.h"
class C : public B {
}
#endif /* !defined(C_H) */
可以看出,B重写了A的方法get()。重写方法返回一个指向相应子类的指针,我猜想这是C++中有效的,这是因为协方差。
我不知道的是如何通知编译器,C确实是B的一个子类,因此重写方法是有效的
在B.h中向前声明C,就像代码中看到的那样,是不够的,因为它没有说明C的超类
在B.h中包含C.h将是循环的,因为C.h已经包含B.h。后者是必需的,因为仅仅向前声明超类是不够的
那能做什么呢
编辑两条评论
1其中一张海报声称,以下内容在Java中是不可能的,因此我添加了一个Java版本:
A.java:
class A {
public B get() {
return null;
}
}
B.java:
class B extends A {
public C get() {
return null;
}
}
C.java:
class C extends B {
}
它编译得很好
2.我不坚持编撰这种有点奇怪的案例。如果它们不能被翻译成C++中的可读代码,那么,后端将只会出现错误消息。事实上,我更感兴趣的是解决C++中的循环依赖的一般方法。
编辑2
谢谢大家,我对这个网站的效率印象深刻。我的结论是,因为:
Artur当您通过机器生成代码时,可以使用一些肮脏的技巧
class B;
class A {
public: virtual B* CLASS_A_get();
}
class C;
class B : public A {
public:
virtual B* CLASS_A_get();
virtual C* CLASS_B_get();
}
class C : public B {
}
// In B's .cpp file, you can include C.h
#include "C.h"
B* B::CLASS_A_get() {
return CLASS_B_get();
}
这里有严重的设计问题 为了使虚函数功能正常工作,类A、B和C中的所有get()函数都应该具有相同的签名 将所有内容更改为: 虚拟A*get() 然后
这就是你想要的吗?我不确定你对问题来源(编写编译器后端)的解释是否真实,因为(1)所提供的代码甚至不正确,没有分号,我认为编写编译器的人会设法提供正确的代码,(2)Java代码中不会自然出现问题,我不确定它是否可以直接用Java表达,除非我在下面介绍的解决方法(在这种情况下,你不需要问),并且(3)这不是一个困难的问题,也不是一个编译器编写者会遇到的问题 也就是说,我强烈怀疑这是作业 也就是说,您只需自己实现协方差,例如:
class B;
class A
{
private:
virtual B* virtualGet() { ... }
public:
B* get() { return virtualGet(); }
};
class C;
class B
: public A
{
private:
virtual B* virtualGet() { ... }
public:
C* get() { return static_cast<C*>( virtualGet() ); }
};
class C
: public B
{};
B类;
甲级
{
私人:
虚拟B*virtualGet(){…}
公众:
B*get(){return virtualGet();}
};
丙级;;
B类
:公众A
{
私人:
虚拟B*virtualGet(){…}
公众:
C*get(){return static_cast(virtualGet());}
};
C类
:公共B
{};
<> P>这与实现协变SyrScor结果相同,除了StaskPosiple结果,可以更依赖C++支持协变原始指针结果。
这是一项众所周知的技术
干杯&hth.,您可以自己干净地实现协方差,无需依赖编译器,也无需任何强制转换。下面是一个包含一些循环依赖项的标题:
class A
{
public:
A* get() { return get_A(); }
private:
virtual A* get_A();
};
class B : public A
{
public:
C* get() { return get_C(); }
private:
virtual A* get_A();
virtual C* get_C();
};
class C : public A
{
public:
B* get() { return get_B(); }
private:
virtual A* get_A();
virtual B* get_B();
};
根据B::get_C实现B::get_A,根据C::get_B实现C::get_A。所有get_*都在cxx文件中实现。您可以这样做,因为这三个类都已完全定义。用户总是调用get()
抱歉,如果格式错误,我是从手机发帖的
编辑:使用静态强制转换的解决方案并不总是适用的,例如,当涉及虚拟继承时(您将需要动态强制转换)。这根本不能解决他的问题,因为派生类必须有新的签名。@Sharath正如我对另一张海报所说的,问题是关于实现编译器后端,与设计无关。不过,我不同意,一般来说,设计错误是一个设计错误,因为重写函数是一个子类型,因为它是java中的一个正常的实践。C++中,你想做的事情毫无意义。我已经十年没有使用Java了,所以我不能对Java发表评论。我在想一个大致相同的答案。但是当编译器需要调用一个
get()
方法时,它需要决定调用哪个方法。我认为这是一个可以解决的问题,前提是所有三个(或N个)类的头文件都在生成代码的范围内可见。@DeadMG这很聪明,但不幸的是,filt只能在实现文件中使用,当头文件将被其他程序员使用。@ AraTaj:你是说头文件会被人们自己编写C++代码使用吗?从java生成C++是很困难的,不想让它对程序员友好C++。无论如何,你不能这么做,因为没有办法将java的GC转换成C++的内存管理。Dan:生成最派生的调用-例如,如果您有一个B*,则调用类_B_get()。@Dan@DeadMG I使用finalize()模拟析构函数。Java程序员必须
class A
{
public:
A* get() { return get_A(); }
private:
virtual A* get_A();
};
class B : public A
{
public:
C* get() { return get_C(); }
private:
virtual A* get_A();
virtual C* get_C();
};
class C : public A
{
public:
B* get() { return get_B(); }
private:
virtual A* get_A();
virtual B* get_B();
};