c ++: точный тип содержащихся объектов без приведения

У меня есть классический пример иерархии фигур ...

struct Shape { // abstract type
    Shape (int x, int y);

    int x;
    int y;
};

struct Rectangle : public Shape {
    Rectangle (int x, int y, int w, int h);

    int w;
    int h;
};

struct Circle : public Shape {
    Circle (int x, int y, int r);

    int r;
};

контейнер Shapes, заполненный прямоугольниками и кругами

std::list<Shape*> container;

и функции печати (в моем случае это функции обнаружения столкновений)

void print_types (Shape&, Shape&) {
    std::cout << "Shape, Shape" << std::endl;
}

void print_types (Rectangle&, Rectangle&) {
    std::cout << "Rectangle, Rectangle" << std::endl;
}

void print_types (Rectangle&, Circle&) {
    ...

Конечно, когда я это делаю:

std::list<Shape*> it;
Rectangle r (0, 0, 32, 32);

for (it = container.begin(); it != container.end(); it++)
     print_types(r, **it);

Я не хочу печатать только линии «Форма, Форма». Я знаю виртуальные методы, dynamic_cast и посетителей. Но есть ли какой-нибудь элегантный способ выйти из этого без этих решений и сохранить мои внешние функции?


person Simon    schedule 03.05.2011    source источник
comment
Элегантный способ - использовать язык в том виде, в котором он был разработан. Это означает виртуальные методы, dynamic_cast и посетителей.   -  person Mark Ransom    schedule 04.05.2011
comment
@Mark: Хотя, в идеале, не dynamic_casts ...   -  person Oliver Charlesworth    schedule 04.05.2011
comment
@Oli, у каждой особенности языка есть причина. Однажды я нашел полезный пример использования dynamic_cast. Если вы не согласны, вы можете оставить здесь заметку: stackoverflow.com/questions/28080/how-bad-is-dynamic-casting/   -  person Mark Ransom    schedule 04.05.2011
comment
@Mark: Я согласен с тем, что иногда используется для dynamic_cast.   -  person Oliver Charlesworth    schedule 04.05.2011


Ответы (4)


Вам, вероятно, следует придерживаться виртуальных функций и иметь только одну функцию print_types

void print_types(Shape&)

Затем добавьте виртуальную функцию PrintName в базовый класс и переопределите ее в производном классе.

Это самый элегантный способ.

person Pepe    schedule 03.05.2011
comment
+1: Это более чистое решение, чем мое предложение о двойной отправке. - person Oliver Charlesworth; 04.05.2011
comment
Фактически, я не печатаю типы, а проверяю коллизии между формами ... может, мне стоит отредактировать свой первоначальный пост. - person Simon; 04.05.2011

Короткий ответ - нет, вызовы функций разрешаются во время компиляции. Таким образом, нет способа (AFAIK) сделать это с вашими существующими бесплатными функциями.

Я считаю, что вам придется вложиться в механизм двойной отправки или сделайте то, что предлагает @Peter в своем ответе (что звучит более элегантно).

person Oliver Charlesworth    schedule 03.05.2011
comment
Я уже пробовал это решение, оно работает отлично, но я искал более простой способ сделать это. - person Simon; 04.05.2011

Я не могу назвать это элегантным, и у него есть несколько подводных камней, но классический способ сделать это до dynamic_cast заключался в том, чтобы иметь virtual функцию, скажем virtual char* name(), в Shape и каждый производный класс переопределял эту функцию, чтобы вернуть правильное имя.

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

person Max Lybbert    schedule 03.05.2011

Отвечая на редактирование проблемы:

Лучшее решение по-прежнему - найти виртуальный метод, который доставит то, что требуется. Для обнаружения столкновений форм, возможно, у вас может быть функция, которая преобразует каждую форму в многоугольник (набор линий) и выполняет обнаружение на этом.

C ++ использует тип, который вы объявили во время компиляции, чтобы выбрать, какую перегруженную функцию вызывать; у него нет возможности сделать этот выбор во время выполнения. Вот почему вы каждый раз видите на выходе «Форма, Форма». Есть способ помочь компилятору, но это будет утомительно. Попробуйте преобразовать каждую Shape * в соответствующий тип, и, если это удастся, вы можете вызвать более конкретную функцию.

Я на самом деле не сторонник этого; вы можете увидеть, как он выходит из-под контроля всего с двумя формами, представьте, насколько уродливым он становится, когда вы добавляете больше! Тем не менее, он показывает, как делать то, что вы пытались сделать на C ++.

void print_types (Rectangle*, Rectangle*) {
    std::cout << "Rectangle, Rectangle" << std::endl;
}

void print_types (Rectangle*, Circle*) {
    ... 

void print_types (Rectangle* left, Shape* right) {
    Rectangle* rightRect = dynamic_cast<Rectangle*>(right);
    if (rightRect != NULL) {
        print_types(left, rightRect);
        return;
    }
    Circle* rightCirc = dynamic_cast<Circle*>(right);
    if (rightCirc != NULL) {
        print_types(left, rightCirc);
        return;
    }
    throw /* something to indicate invalid shape */;
}

void print_types (Circle* left, Shape* right) {
    ...

void print_types (Shape* left, Shape* right) {
    Rectangle* leftRect = dynamic_cast<Rectangle*>(left);
    if (leftRect != NULL) {
        print_types(leftRect, right);
        return;
    }
    Circle* leftCirc = dynamic_cast<Circle*>(left);
    if (leftCirc != NULL) {
        print_types(leftCirc, right);
        return;
    }
    throw /* something to indicate invalid shape */;
}
person Mark Ransom    schedule 03.05.2011