Концепция полиморфизма C ++

0

Мне нужно какое-то объяснение в конкретном абзаце, который я читаю здесь.

Итак, в первом примере:

// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
  { width=a; height=b; }
};

class CRectangle: public CPolygon {
public:
int area ()
  { return (width * height); }
};

class CTriangle: public CPolygon {
public:
int area ()
  { return (width * height / 2); }
};

int main () {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = &rect;
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << endl;
cout << trgl.area() << endl;
return 0;
}

Почему я не могу просто ссылаться на ppoly1->area() и ppoly2->area()? Поскольку оба указателя (даже если они относятся к классу CPolygon), указывают на адрес, содержащий объекты производного класса.

Теги:
polymorphism

2 ответа

7
Лучший ответ

Поскольку нет virtual int area(); объявленный в CPolygon, нет никакой связи для этого метода с объектами класса CPolygon вообще.

В принципе, вы ожидаете, что поиск метода произойдет во время выполнения (например, в Python, JavaScript и других языках), но в C++ это происходит во время компиляции. Вышеупомянутый код неявно говорит компилятору C++: " CRectangle и CTriangle имеют методы с именем area но они не имеют ничего общего друг с другом и не имеют ничего общего с CPolygon ".

Помните, что компилятор C++ часто не видит всех подклассов данного класса во время компиляции.


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

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

Например, в этом случае, если весь ваш код был одной функцией, которая создала CTriangle а затем вызвала его метод area чтобы получить область, распечатать ее и завершить, тогда да, там не так много. Код - не что иное, как прославленный калькулятор с дополнительным глаголом.

Но в реальном мире у вас нет роскоши всего в одной функции или в исходном файле. Поэтому кусок кода может знать, что он имеет указатель на CPolygon но не на какой конкретный подкласс. И все же этот код должен знать его область. Например, функция, которая вычисляет экструдированный объем с учетом многоугольника поперечного сечения и толщины:

int extruded_volume(CPolygon* poly, int thickness)
{
    return thickness * poly->area();
}

Представьте, что эта функция объявлена в Extrusion.h и определена в Extrusion.cpp. Кроме того, представьте, что каждый из CPolygon, CRectangle и CTriangle отдельно объявляется в .h файлах и определен в файлах .cpp названных в честь классов соответственно. Затем Extrusion.h требуется только #include "CPolygon.h" и может продолжать свою жизнь, ничего не зная о различных подклассах. Это работает тогда и только тогда, когда area является виртуальным методом.

Это также позволяет CPolygon подклассы CPolygon позже. Например, подкласс CEllipse представляющий эллипс, чья "ширина" и "высота" были его малой и большой осями, соответственно:

class CEllipse: public CPolygon {
public:
int area ()
  { return M_PI * (width/2) * (height/2); }
};

Поскольку мы использовали виртуальный метод, CEllipse* можно было передать в extruded_volume без изменения последнего кода.

Если area не была виртуальным методом, вам понадобится отдельная версия extruded_volume для каждого возможного многоугольника:

template<typename T>
int extruded_volume(T* poly, int thickness)
{
    return thickness * poly->area();
}

В этом случае раздувание кода является незначительным, потому что там не так много кода, но в более крупной системе это вполне может быть неприемлемым. Также обратите внимание, что все, что вызывает extruded_volume, скорее всего, также будет шаблоном.

  • 0
    Зачем мне использовать указатель базового класса, чтобы указывать на члены производного класса (путем реализации виртуальных функций), когда я могу просто создать объект производного класса, вызвать его функцию области и выполнить свою работу?
  • 0
    Смотрите дополнения выше. я решил пропустить случай «что делать, если вы не можете использовать шаблоны и должны выполнять идентификацию подкласса во время выполнения», потому что это уже tl; dr.
Показать ещё 1 комментарий
0

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

Существует два способа обойти это

В ролях:

((*CRectangle)ppoly1)->area() //C style
static_cast<*CRectangle>(ppoly1)->area //C++ style 

Виртуальный метод:

class CPolygon {
 public:
 virtual int area() { return (0); }
 ...
  • 0
    На самом деле, стиль C ++ должен требовать, чтобы вы использовали dynamic_cast вместо static_cast , и вы должны проверить результат dynamic_cast потому что он вернет нулевой указатель, если исходный указатель фактически не указывает на производный тип (например, он пришел из CTriangle ). Путь C бросает все предостережения на ветер и верит, что ваш указатель является законным. Используйте static_cast для перехода от производного класса к его базовому классу. Кроме того, * идет после имени типа.
  • 0
    В некотором коде вы найдете dynamic_cast используемый в форме if(CRectangle* pr = dynamic_cast<CRectangle*>(ppoly1)) { cout << pr->area() << endl; } else { cerr << "Not a rectangle." << endl; } хотя это беспокоит меня, потому что я не люблю видеть = внутри if(...) хотя здесь это правильно.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню