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

Я создал пример, чтобы лучше объяснить, что я имею в виду:

#include <iostream>

using namespace std;

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};


class rectangle : public poligon
{
    double GetArea() { return l*h; }
    void GetType() { cout << "rectangle" << endl; }
};


void main()
{
    poligon* X;
    int input;

    cout << "1 for triangle and 2 for rectangle: ";
    cin >> input;

    if (input == 1)
    {
        X = new triangle;
    }
    else if (input == 2)
    {
        X = new rectangle;
    }
    else
    {
        cout << "Error";
    }

    X->h = 5;
    X->l = 6;

    X->GetType();
    cout << "Area = " << X->GetArea() << endl;

    if (input == 2)
    {
        cout << "Diangonal = " << X->GetDiag() << endl;    // NOT POSSIBLE BECAUSE " GetDiag()" IS NOT A METHOD OF "poligon" CLASS !!!
    }
}

Очевидно, что метод X->GetDiag() в конце main нельзя использовать, потому что он не является методом класса "poligon". Какова правильная реализация программы с этой логикой?

4
Mattia 3 Сен 2017 в 11:04

7 ответов

Лучший ответ

Введите метод в базовый класс

virtual bool boHasDiagonal(void) =0;

Объявить безоговорочно в базовом классе:

virtual double GetDiag();

Реализуйте это по-разному в обоих производных классах:

virtual bool boHasDiagonal(void) {return true;} // rectangle
virtual bool boHasDiagonal(void) {return false;} // triangle

Изменить строку вывода:

if (X->boHasDiagonal())
{cout << "Diangonal = " << X->GetDiag() << endl;}

Для приятного прикосновения к паранойе (на мой взгляд, здоровому состоянию ума для программиста) используйте концепцию Gluttton реализации по умолчанию GetDiag(), которая сигнализирует об ошибке (как в его ответе здесь).

Для случая многих полигонов мне нравится предложение Ракете 1111 в комментарии.

3
Yunnosch 4 Сен 2017 в 11:28

Определите метод в базовом классе, который определяет исключение при реализации:

class poligon
{
public:
    virtual double GetDiag()
    {
        throw std::logic_error ("Called function with inappropriate default implementation.");
    }
};

В классе, который имеет значимую реализацию, переопределите его:

class rectangle : public poligon
{
    double GetDiag() override
    {
        return diagonale;
    }
};

Использование:

int main () {
    try {
        X->GetDiag();
    }
    catch (...) {
        std::cout << "Looks like polygon doesn't have diagonal." << std::endl;
    }
}
1
Gluttton 3 Сен 2017 в 08:25

Понижение обычно является признаком плохого дизайна и редко требуется на практике.

Я не понимаю, зачем это нужно в данном конкретном случае. Вы отказались от информации о том, какой тип у вас есть без причины. Альтернативой может быть:

void printDiagonal(const triangle& tri)
{
    std::cout << "Diangonal = " << tri.GetDiag() << std::endl;
}

void process(poligon& p)
{
    p.h = 5;
    p.l = 6;

    p.GetType();
    std::cout << "Area = " << p.GetArea() << std::endl;
}

int main()
{
    int input;

    std::cout << "1 for triangle and 2 for rectangle: ";
    std::cin >> input;

    if (input == 1)
    {
        triangle tri;
        process(tri);
        printDiagonal(tri);
    }
    else if (input == 2)
    {
        rectangle rect;
        process(rect);
    }
    else
    {
        std::cout << "Error\n";
    }
}

Демонстрационная версия.

0
Chris Drew 3 Сен 2017 в 12:39

Как уже говорили другие, вы можете использовать dynamic_cast для изменения статического типа в вашей программе, добавления метода в базовый класс с псевдо-реализацией или использования какой-либо формы переключения типов. Тем не менее, я бы посчитал все эти ответы признаками недостатка дизайна в вашей программе и отклонил бы код. Все они кодируют предположения о типах, существующих в вашей программе, в код и создают бремя обслуживания. Представьте, что вы добавляете новые типы фигур в вашу программу. Затем вам нужно найти и изменить все места, где вы dynamic_cast ваши объекты.

Я думаю, что ваш пример иерархии в первую очередь неверен. Когда вы объявляете базовый класс для уловок и извлекаете из него треугольники, вся цель полиморфизма состоит в том, чтобы иметь возможность идентично обращаться с подобными объектами. Поэтому все, что не является обычным поведением ( не реализация ), помещается в базовый класс.

class poligon
{
public:
    double h, l;
    void setPoligon(double h, double l) {
        this->h = h;
        this->l = l;
    }
    virtual double GetArea() = 0;
    virtual void GetType() = 0;
};


class triangle : public poligon
{
    double GetArea() { return l*h / 2; }
    void GetType() { cout << "triangle" << endl; }
    double GetDiag() { return sqrt(l*l + h*h); }
};

Вы прямо говорите, что я могу заменить любой экземпляр многоугольника на экземпляр треугольника везде в вашей программе. Это принцип замещения Лискова. А как насчет кругов? У них нет роста и длины. Можете ли вы использовать прямоугольник везде, где вы ожидаете многоугольник? В настоящее время вы можете, но полигоны могут иметь больше ребер, быть самопересекающимися и т. Д. Я не могу добавить новое ребро в прямоугольник, иначе это будет прямоугольник.

Есть несколько решений, но так как это вопрос дизайна, решение зависит от того, что вы хотите сделать с объектами.

0
Jens 3 Сен 2017 в 10:14

Вы можете использовать dynamic_cast.

dynamic_cast<triangle*>(X)->GetDiag();

Обратите внимание, что у вас уже есть ошибка: вы создаете triangle, если input == 1, но вы получаете диагональ, если input == 2. Кроме того, вышеприведенное не совсем безопасно, потому что dynamic_cast может вернуть nullptr, если преобразование недопустимо.

Но было бы лучше проверить, успешно ли dynamic_cast, тогда вы также можете отказаться от проверки input == 2:

if (triangle* tri = dynamic_cast<triangle*>(X))
    std::cout << "Diagonal = " << tri->GetDiag() << '\n';
0
Rakete1111 3 Сен 2017 в 08:14

Используйте динамическое приведение, чтобы проверить, действительно ли указатель базового класса является треугольник, вот так:

int main()
{
    ...
    if(triangle* t = dynamic_cast<triangle*>(X))
        std::cout << "Triangle's diagonal = " << t->GetDiag() << std::endl;
    return 0;
}

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

0
gsamaras 3 Сен 2017 в 08:21

Вы используете dynamic_cast для доступа к методам подкласса. Он возвращает nullptr, если он не является производным от класса. Это называется понижением , когда вы спускаетесь по дереву классов:

triangle* R = dynamic_cast<triangle*>(X);
if(R) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

Редактировать . Вы можете поместить объявление в первой строке в условие if, которое выходит за рамки оператора if:

if(triangle* R = dynamic_cast<triangle*>(X)) {
    cout << "Diagonale = " << R->GetDiag() << '\n';
};

if(rectangle* R = ...) {...}; // reuse of identifier

Если вы хотите, чтобы multiple subclasses имел функцию GetDiag, которую вы можете наследовать от poligon - класса и другого diagonal - класса. Класс diagonal - определяет только функцию GetDiag и не имеет ничего общего с классом polygon:

class polygon {
    // stays the same
};

class diagonal {
    virtual double GetDiag() = 0;
};

class triangle : public polygon, public diagonal {
    // body stays the same
};

Как и выше, вы получаете доступ к методам через приведение с dynamic_cast, но на этот раз вы приводите к типу diagonal. На этот раз это боковой бросок , потому что poligon не имеет ничего общего с diagonal, поэтому вы идете боком в дереве.

   polygon         diagonal
    |   |             |
    |   |_____________|
    |          |
    |          |
rectangle   triangle
0
cmdLP 3 Сен 2017 в 10:09