C ++协方差/覆盖/循环问题

时间:2011-07-06 15:44:09

标签: c++ covariance circular-dependency override

我正在编写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
#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

谢谢大家,我对这个网站的效率印象深刻。

我的结论是,因为:

  1. 生成的头文件将由其他程序员使用;
  2. 从您的答案中猜测,可能没有解决方案可以生成简单,可读的头文件;
  3. 涉及返回类型的循环引用可能很少见;
  4. 我避免使用C ++,因为除其他外,它允许这样的解决方案 - 我知道C ++有它的用途,但就个人而言,我更喜欢语法更简单的语言,比如Java;
  5. 后端将:

    1. 尽可能使用前瞻声明;
    2. 否则将使用包括,检查后者是否为循环;如果是,它将失败并显示错误消息。
    3. 干杯, 阿图尔

4 个答案:

答案 0 :(得分:3)

当你用机器生成代码时,可以使用一些肮脏,肮脏的技巧。

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();
}

答案 1 :(得分:2)

您可以自己干净地实现协方差,而无需依赖编译器且无需任何强制转换。这是一个带有一些循环依赖的标题:

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();    
};

根据C :: get_B,用B :: get_C和C :: get_A实现B :: get_A。所有get_ *都在cxx文件中实现。您可以因为所有三个类已经完全定义。用户总是调用get()。

如果格式错误,我很抱歉,我是用手机发帖的。

编辑:使用静态强制转换的解决方案并不总是适用,例如当涉及虚拟继承时(那么你将需要动态强制转换)。

答案 2 :(得分:1)

我不确定你对问题起源的解释(编写编译器后端)是真实的,因为(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
{};

它与实现协变智能指针结果相同,只是对于智能指针结果,人们可以更多地依赖C ++对协变原始指针结果的支持。

这是一项众所周知的技术。

干杯&amp;第h。,

答案 3 :(得分:0)

严重的设计问题。

要使虚函数功能起作用,A,B和C类中的所有get()函数都应具有相同的签名。

将所有内容更改为:

虚拟A * get();

然后

A* ptr1 = new B;
ptr1->get(); // this will call the get function of class B.

A* ptr2 = new C;
ptr2->get(); // this will call the get function of class C.

这是你想要的吗?