Родительская функция, принимающая дочернюю ссылку в качестве параметра (или Приключения трех взаимозависимых классов)

Извините, если этот вопрос уже задавался, но я искал уже несколько часов и нахожу ответы только на похожие, но не совсем одинаковые проблемы, и ни один из них не работает в моем случае. Я начинающий программист, поэтому, скорее всего, мой код не работает либо из-за отвратительного дизайна, либо из-за очень простой глупой ошибки. У меня есть альтернативный дизайн, но я хотел бы знать, является ли дизайн буквально невозможным или я просто неправильно его реализую.

Итак, вот что я пытаюсь сделать: я пишу код обнаружения 2D-столкновений, который должен работать как для многоугольников, так и для кругов. Многоугольники должны обнаруживать столкновения с кругами и другими полигонами, а круги должны обнаруживать столкновения с полигонами и другими кругами. Я подумал, что было бы неплохо иметь возможность поместить все сталкивающиеся объекты (многоугольники и круги) в один std::vector под названием "Collidables", а не иметь отдельный вектор для каждого класса. Поэтому я создал абстрактный класс Shape, который станет родителем для Circle и Polygon и будет содержать виртуальные функции обнаружения столкновений. Тогда вектор Collidables будет последовательностью указателей Shape. Вот как выглядят эти классы:

//in shape.h:
#ifndef __SHAPE_H_INCLUDED__
#define __SHAPE_H_INCLUDED__
class Polygon;
class Circle;

class Shape {
//number of functions and variables irrelevant to the issue
//relevant functions, note that Vector with capital 'V' is not std::vector but a class I wrote myself:
    virtual Vector* detectCollision(const Shape& Collidable) const = 0;
    virtual Vector* detectCollision(const Polygon& Collidable) const = 0;
    virtual Vector* detectCollision(const Circle& Collidable) const = 0;
};
#endif


//in polygon.h
#ifndef __POLYGON_H_INCLUDED__
#define __POLYGON_H_INCLUDED__
#include "shape.h"
//edit: deleted #include "circle.h", this is now included in polygon.cpp
//edit: deleted redundant Circle forward declaration

class Polygon: public Shape {
    //irrelevant stuff
    Vector* detectCollision(const Shape& Collidable) const {return Collidable.detectCollision(*this)};
    Vector* detectCollision(const Polygon& Collidable) const;
    Vector* detectCollision(const Circle& Collidable) const;
};
#endif


//in circle.h
#ifndef __CIRCLE_H_INCLUDED__
#define __CIRCLE_H_INCLUDED__
#include "shape.h"
//edit: deleted #include "polygon.h", this is now included in circle.cpp
class Circle: public Shape {
    //irrelevant stuff
    Vector* detectCollision (const Shape& Collidable) const {return Collidable.detectCollision(*this);}
    Vector* detectCollision (const Polygon& Collidable) const;
    Vector* detectCollision (const Circle& Collidable) const;
};
#endif

Идея здесь в том, что любая фигура должна иметь возможность обнаруживать столкновение с любой другой фигурой, даже не зная, обнаруживает ли она столкновение с многоугольником или с кругом во время вызова функции detectCollision. Представьте, что вы круг, и вы перебираете Collidables std::vector. Первый элемент — это Polygon, но вы этого не знаете, вы знаете только, что вы — круг и что параметр, который получает ваша функция detectCollision, является ссылкой на Shape. Итак, вы говорите этой форме: «Посмотри, я не знаю, кто ты, но я круг. Вот, я передам себя в качестве параметра твоей функции detectCollision, и ты сможешь вернуть мне результат». Таким образом, Shape вызывает свой виртуальный метод detectCollision(const &Circle), который затем передается в метод detectCollision(const &Circle) его дочернего элемента Polygon, который имеет фактическую рабочую реализацию и который должен затем вернуть указатель на точку пересечения ( или нулевой указатель, если нет пересечения).

Это работало нормально, когда были написаны только классы Shape и Polygon, но как только я добавил класс Circle, мой код больше не компилировался. Ошибка, которую я получаю:

В файле, включенном из polygon.cpp:1:0: polygon.h: В функции-члене 'виртуальный вектор* Polygon::detectCollision(const Circle&) const': polygon.h:45:78: ошибка: недопустимое использование неполного типа ' const struct Circle' shape.h:8:7: ошибка: предварительное объявление 'const struct Circle'

Я предполагаю, что это проблема дизайна из-за нелепого количества циклических зависимостей, происходящих здесь: Shape зависит от своих дочерних элементов, потому что некоторые из его виртуальных функций принимают дочерние элементы в качестве параметра, Polygon зависит от Shape (потому что он наследуется от него) и Circle (потому что у него есть функция detectCollision, принимающая параметр Circle&), а Circle зависит от Shape (потому что он наследуется от него) и Polygon (потому что у него есть функцияDetectCollision, принимающая параметр Polygon&). Самый простой выход, вероятно, просто отказаться от идеи помещать многоугольники и окружности в один контейнер последовательности и просто поместить их в два отдельных std::vectors. Тем не менее, я хотел бы знать, является ли мой первый дизайн просто невозможным по своей сути, возможным, но уродливым, или надежным, но неправильно реализованным. Обычно я предпочитаю находить подобные вещи самостоятельно, но после нескольких часов поиска и пробования различных решений я должен признать, что просто не понимаю этой проблемы.

(изменить: некоторые изменения кода, предложенные Дэйвом)

РЕДАКТИРОВАТЬ, ссылка на полный источник, если кому-то нужна дополнительная информация: https://github.com/KoenP/medieval-melee-combat-cpp

Я компилирую только файлы polygon.cpp, circle.cpp, vector.cpp и line.cpp; test.cpp в настоящее время не имеет значения. Не то, чтобы вектор и линия имели большое значение для этой ошибки, их некомпиляция (только компиляция многоугольника и круга) дает точно такую ​​​​же ошибку. Точная команда, которую я использую, это «g++ -o test circle.cpp polygon.cpp line.cpp vector.cpp».


person KoenP    schedule 25.11.2012    source источник


Ответы (1)


Такой дизайн довольно распространен. Чтобы это работало, нужно перенаправить объявление в заголовках (не #include) и #include в cpps. Вы были на правильном пути...

В circle.h и polygon.h включите только shape.h. Удалите class Circle; из polygon.h (это лишнее, так как вы уже получили его, включив Shape.h). Затем в polygon.cpp, #include "circle.h". А в circle.cpp, #include "polygon.h".

person David    schedule 25.11.2012
comment
Спасибо, что нашли время, чтобы помочь мне! Боюсь, я все еще не могу заставить его работать... Вот чем я сейчас занимаюсь: компилирую файлы polygon.cpp и circle.cpp (g++ -o test polygon.cpp circle. спп). polygon.cpp включает в себя polygon.h и circle.h; Circle.cpp делает то же самое (в обратном порядке, не уверен, имеет ли это значение). Затем polygon.h включает shape.h (и другие файлы, такие как ‹math.h›, ‹vector› и некоторые из моих собственных классов). Circle.h также включает shape.h. shape.h forward объявляет как Circle, так и Polygon. Ошибка, которую я получаю сейчас (следующий комментарий): - person KoenP; 25.11.2012
comment
В файле, включенном из polygon.cpp:1:0: polygon.h: В функции-члене 'виртуальный вектор* Polygon::detectCollision(const Circle&) const': polygon.h:45:78: ошибка: недопустимое использование неполного типа ' const struct Circle' shape.h:8:7: ошибка: предварительное объявление 'const struct Circle' - person KoenP; 25.11.2012
comment
@KoenP, почему он говорит const struct circle? circle это class, а не struct. Вы где-то смешиваете ключевые слова? Circle не должно быть неполным в любом контексте, где есть #included circle.h (например, в polygon.cpp) - person David; 25.11.2012
comment
Я тоже в полном неведении по этому поводу. Я на 100% уверен, что вообще не использовал никаких структур в проекте, поэтому я был сбит с толку, когда компилятор описал Circle как структуру. Я также не понимаю, почему компилятор считает Circle незавершенным. Сначала я подумал, что мог забыть поместить реализацию виртуальной функции в shape.h в circle.h или что я не определил функцию в circle.cpp, которая была объявлена ​​в его заголовке. Я проверил оба, и ни один из них не является проблемой. И в любом из этих случаев компилятор был бы более конкретным для начала... - person KoenP; 26.11.2012
comment
Я искал его снова, и я почти уверен, что структура ничего не значит. Например, здесь (ссылка) очень похожая проблема, и сообщаемая ошибка точно такая же. Компилятор также называет его класс MainWindow структурой. Вероятно, просто компилятор является компилятором. Я думаю, можно с уверенностью заключить, что проблема заключается в том, что циклическая зависимость либо невозможна, либо неправильно написана (последнее, вероятно, более вероятно). - person KoenP; 26.11.2012
comment
Кстати, я помню, что эта ссылка была одной из вещей, которые я нашел при поиске своей ошибки, прежде чем разместить здесь свой вопрос. После прочтения вам может сойти с рук прямое объявление MainWindow в Login_Dialog.h, если вы только вперед объявляете указатель на тип, я даже пытался изменить все функции detectCollision, чтобы они принимали указатели на коллизируемые вместо ссылок, хотя это было в основном акт отчаяния, поскольку, на самом деле, ссылка работает в основном так же, как указатель (хотя не уверен в этом). Во всяком случае, это тоже не сработало :( - person KoenP; 26.11.2012
comment
Что интересно, независимо от того, какой класс скомпилирован первым, какой класс объявлен первым в shape.h или какие заголовки включены первыми в cpp, ВСЕГДА Circle считается незавершенным, и компилятор ВСЕГДА указывает на предварительное объявление Circle. в форме.ч. Я нахожу это странным, я думал, что если проблема связана исключительно с ошибочным включением/объявлением, компилятор должен пожаловаться на Polygon, если вы поменяете порядок, в котором они включаются или компилируются. Так что, возможно, действительно есть проблема с Circle, но я уже не знаю, что искать... - person KoenP; 26.11.2012
comment
ДА, Я НАШЕЛ ЭТО :D! Проблема заключалась в том, что я не только объявил, но и определил метод обнаружения столкновения Polygon (const Circle&) в файле polygon.h, а не в файле cpp. Поскольку он просто вызывает функцию CircleDetectCollision(const Polygon&) (чтобы избежать дублирования кода), я подумал, что реализация была достаточно короткой, чтобы поместить ее в заголовок, и, по-видимому, это не работает. Честно говоря, я не очень понимаю, почему, но, по крайней мере, код снова компилируется :). ОГРОМНОЕ СПАСИБО за помощь, мне очень приятно! - person KoenP; 26.11.2012