一个C++协方差/压倒一切/循环问题

发布于 2024-11-19 06:18:06 字数 1643 浏览 2 评论 0原文

我正在编写 Java 子集的编译器后端。后端编写C++代码。不过,有一些假设的 Java 代码我不知道如何转换为 C++。

以下代码显示了一个示例问题。 A由B扩展,B由C扩展,这里分别是三个头文件Ah、Bh和Ch:


#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 的子类,因此重写方法是有效的。

正如代码中所见,在 Bh 中对 C 进行前向声明是不够的,因为它没有提及 C 的超类。

将 Ch 包含在 Bh 中将是循环的,因为 Ch 已经包含 Bh 。后者是必需的,因为仅具有超类的前向声明是不够的。

可以用它做什么呢?

编辑两点评论。

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;

后端将:

  1. 尽可能使用前向声明;
  2. 否则它将使用includes,检查后者是否是循环的;如果是,它将失败并显示错误消息。

干杯, 阿图尔

I am writing a backend of a compiler of a subset of Java. The backend writes C++ code. There is some hypothetical Java code, though, that I do not known how to translate to C++.

An example problem is shown in the following code. A is extended by B, B is extended by C, and here are respective three header files A.h, B.h and 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) */

As can be seen, B overrides A's method get(). The overriding method returns a pointer to a respective subclass, which I guess is valid in C++, thanks to covariance.

What I do not know, is the way of informing the compiler, that C is indeed a subclass of B, and thus, that the overriding method is valid.

A forward declaration of C within B.h, just as the one seen in the code, is not enough, as it says nothing about C's superclasses.

An inclusion of C.h within B.h would be circular, as C.h already includes B.h. The latter is needed, because it is not enough to have only a forward declaration of the superclass.

What can be done with that?

EDIT Two remarks.

1 One of the poster claims, that the following is impossible in Java, so I add a Java version:

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 {
}

It compiles just fine.

2 I do not insist on compiling such somewhat strange cases. If they can not be translated to a readable code in C++, then fine, the backend will just fail with an error message. In fact, I am more interested in a general way of resolving the circular dependencies like that in C++.

EDIT 2

Thank you all, I am impressed by the efficiency of this site.

I concluded that, because:

  1. the header files generated are going to be used by other programmers;
  2. guessing from your answers, there is likely no solution that produces simple, readable header files;
  3. circular references involving return types are probably rare;
  4. I avoid C++ because, amongst others, it allows for solutions like that --
    I know C++ has its uses, but personally, I prefer languages that have a simpler grammar, like Java;

the backend will:

  1. use forward declarations where possible;
  2. otherwise it will use includes, checking if the latter is circular; if yes, it will fail with an error message.

Cheers,
Artur

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

昵称有卵用 2024-11-26 06:18:06

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

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

As you're generating code by machine, it's OK to use some dirty, filthy tricks.

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();
}
凝望流年 2024-11-26 06:18:06

您可以自己干净地实现协变,无需依赖编译器,也无需任何强制转换。这是一个具有一些循环依赖性的标头:

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()。

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

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

You can implement covariance yourself, cleanly, without relying on the compiler and without any casts. Here's a header with some circular dependencies:

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

You implement B::get_A in terms of B::get_C and C::get_A in terms of C::get_B. All get_* are implemented in the cxx file. You can because all three classes are already completely defined. The user always calls get().

Sorry if the formatting is wrong, I'm posting from a cell phone.

Edit: the solution with static casts is not always applicable, e.g. when virtual inheritance is involved (you will need dynamic casts then).

深海不蓝 2024-11-26 06:18:06

我不确定你对问题起源(编写编译器后端)的解释是否真实,因为(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++ 对协变原始指针结果的支持。

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

干杯&呵呵,

I'm not sure your explanation of the problem's origin (writing a compiler back-end) is truthful, because (1) the presented code is not even correct, lacking semicolons, and I think one who were writing compiler stuff would manage to present correct code, and (2) the problem does not naturally occur in Java code, and I'm not sure it can even be expressed directly in Java except by the workaround that I show below (in which case you would not need to ask), and (3) it's not a difficult problem, not a problem a compiler writer would struggle with.

That is, I strongly suspect that this is homework.

That said, you simply have to implement the covariance yourself, e.g. like this:

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
{};

It's the same as implementing covariant smartpointer results, except that for smartpointer results one can more rely on C++ support for covariant raw pointer results.

It's a well-known technique.

Cheers & hth.,

关于从前 2024-11-26 06:18:06

这里有严重的设计问题。

为了使虚函数功能发挥作用,A、B 和 C 类中的所有 get() 函数都应该具有相同的签名。

将所有内容更改为:

virtual 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.

这就是你想要的吗?

Serious design issue here.

For the virtual function feature to work, all the get() function in classes A, B and C should have the same signature.

Change everything to:

virtual A* get();

Then

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.

Is this what you want?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文