Проблема ковариации / переопределения / округлости C ++ - PullRequest
3 голосов
/ 06 июля 2011

Я пишу бэкэнд компилятора подмножества 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 переопределяет метод get (). Переопределяющий метод возвращает указатель на соответствующий подкласс, который, я думаю, действителен в C ++, благодаря ковариантности.

То, что я не знаю, это способ информирования компилятора о том, что C действительно является подклассом B, и, следовательно, что переопределяющий метод действителен.

Предварительного объявления C в B.h, как и в коде, недостаточно, поскольку оно ничего не говорит о суперклассах C.

Включение C.h в B.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;

бэкэнд будет:

  1. по возможности использовать предварительные объявления;
  2. в противном случае он будет использовать include, проверяя, является ли последний круговым; если да, произойдет сбой с сообщением об ошибке.

Ура, Артур

Ответы [ 4 ]

3 голосов
/ 06 июля 2011

Поскольку вы генерируете код на машине, все в порядке, чтобы использовать некоторые грязные, грязные уловки.

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();
}
2 голосов
/ 06 июля 2011

Вы можете реализовать ковариацию самостоятельно, без использования компилятора и приведения. Вот заголовок с некоторыми циклическими зависимостями:

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_A в терминах B :: get_C и C :: get_A в терминах C :: get_B. Все get_ * реализованы в файле cxx. Вы можете, потому что все три класса уже полностью определены. Пользователь всегда вызывает get ().

Извините, если форматирование неправильное, я пишу с мобильного телефона.

Редактировать: решение со статическими приведениями не всегда применимо, например, когда задействовано виртуальное наследование (тогда вам понадобятся динамические приведения).

1 голос
/ 06 июля 2011

Я не уверен, что ваше объяснение происхождения проблемы (написание серверной части компилятора) является правдивым, потому что (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
{};

Это то же самое, что и реализация ковариантных результатов smartpointer, за исключением того, что для результатов smartpointer можно больше полагаться на поддержку C ++ дляковариантный необработанный указатель результатов.

Это хорошо известная техника.

Приветствия & hth.,

0 голосов
/ 06 июля 2011

Серьезная проблема дизайна здесь.

Для работы функции виртуальной функции все функции get () в классах A, B и C должны иметь одинаковую сигнатуру.

Измените все на:

Виртуальный 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.

Это то, что вы хотите?

...