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