void use(A &c)
{
const int sz = c.size();
for (int i=0; i !=sz; ++i)
std::cout << c[i] << std::endl;
}
Абстрактные виртуальные методы
Есть два класса контейнера, один из которых является чистым виртуальным, вот они:
Код:
class A
{
public:
virtual int size() = 0;
virtual double operator[](int)=0;
virtual ~A() {}
};
{
public:
virtual int size() = 0;
virtual double operator[](int)=0;
virtual ~A() {}
};
Код:
class B: public A
{
int _size;
double _stor[10];
public:
B():_size(0) { }
B(int s): _size(s) {}
int size() { return _size; }
double operator[](int pos) { return _stor[pos]; }
void push_back(double item) {_stor[_size++] = item;}
};
{
int _size;
double _stor[10];
public:
B():_size(0) { }
B(int s): _size(s) {}
int size() { return _size; }
double operator[](int pos) { return _stor[pos]; }
void push_back(double item) {_stor[_size++] = item;}
};
И есть одна функция, которая проходит по коллекции и просто выводит в консоль содержимое, вот она:
Код:
Код:
B a;
a.push_back(4);
a.push_back(5);
a.push_back(6);
use(a);
a.push_back(4);
a.push_back(5);
a.push_back(6);
use(a);
P.S Страуструп в процессе, не пинайте сильно.
Обычный динамический полиморфизм. a описана как B, а B имеет право быть и A, вот use и разобралась (по таблице вирт. методов). Тип фактического параметра тут сыграл роль.
И что будет, если будет такая иерархия: A -> B -> C. Классы B и C имеют имплементацию оператора. Имплементацию какого класса тогда выбрала бы use?
Что означает эта фраза?
Механизм - поиск в таблице.
Код:
/*
Polymorphism
see C++ Language Tutorial by cplusplus.com
*/
/*
Ключевое св-во порожденных классов - совместимость типа их указателей с типом
указателей на их базовый класс.
Члены класса, которые могут быть переопределены в классах-потомках наз-ся
виртуальными. Их декларируют с приставкой virtual.
Класс в котором декларированы или который наследует виртуальные ф-ции наз-ся
полиморф.
Вирт. ф-ции с добавкой =0 вместо реализации (см. в коде ниже) - чисто виртуал.,
а классы хотя бы с одной такой ф-цией - абстрактные базовые классы.
Нельзя создать объект абстрактного класса, но указатель - можно.
Указатель на абстр. баз. класс можно использовать для указания на объекты
классов-потомков. Это используют для реализации полиморфизма.
*/
// dynamic allocation and polymorphism -----------------------------------------
#include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0; //pure virtual => abstract class
void printarea (void)
{ cout << this->area() << endl; }
};
class CRectangle: public CPolygon {
public:
int area (void)
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void)
{ return (width * height / 2); }
};
int main () {
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
delete ppoly1;
delete ppoly2;
return 0;
}
Polymorphism
see C++ Language Tutorial by cplusplus.com
*/
/*
Ключевое св-во порожденных классов - совместимость типа их указателей с типом
указателей на их базовый класс.
Члены класса, которые могут быть переопределены в классах-потомках наз-ся
виртуальными. Их декларируют с приставкой virtual.
Класс в котором декларированы или который наследует виртуальные ф-ции наз-ся
полиморф.
Вирт. ф-ции с добавкой =0 вместо реализации (см. в коде ниже) - чисто виртуал.,
а классы хотя бы с одной такой ф-цией - абстрактные базовые классы.
Нельзя создать объект абстрактного класса, но указатель - можно.
Указатель на абстр. баз. класс можно использовать для указания на объекты
классов-потомков. Это используют для реализации полиморфизма.
*/
// dynamic allocation and polymorphism -----------------------------------------
#include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0; //pure virtual => abstract class
void printarea (void)
{ cout << this->area() << endl; }
};
class CRectangle: public CPolygon {
public:
int area (void)
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void)
{ return (width * height / 2); }
};
int main () {
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
delete ppoly1;
delete ppoly2;
return 0;
}
Так как в таблице ищутся нужные функции, если два дочерних класса будут иметь имплементацию функции с одной и той же сигнатурой? Возьмётся тот, который "дочерней" или будет ошибка компиляции?
Нет, ошибки не будет. Наоборот ради таких вещей все и придумано :)
Кстати еще одна важная вещь -- полиморфизм действительно динамический. Работает даже во время исполнения.
Классы А и В описаны выше. Я добавляю класс С, который является наследником В, то есть
Код:
class C: public B
{
...
double operator[](int pos) {return _stor[pos];}
...
}
{
...
double operator[](int pos) {return _stor[pos];}
...
}
Алсо, профит от полиморфизма я понимаю какой, но хотелось бы знать, как будет работать всё на более низком уровне.
UPD: допущу, что никак не будет понимать и сломается. Поэтому появляется ещё один вопрос: как будет функция работать, если в B и С перегрузку оператора сделать виртуальной?
Реализацию из C. А общая сигнатура у них у всех еще в классе A задана. Что вас так смущает? Таблица и дает однозначное соответствие, устраняющую путаницу. Я ее на низком уровне себе слабо представляю, но по типу объекта из нее достается нужный вариант ф-ции. Или я вас недопонял..
Хотя, кажется, начинаю понимать. Потому что созданный объект класса С, а не В, да?
Чёрт возьми, если так. Может, это что-то простое, но для меня выглядит ужасно нелогичным.
Верно вы поняли.
Всё, теперь понял. Спасибо. Осталось только, чтобы во всех компиляторах было так.
Все компиляторы в этом плане единомышленники. Таков стандарт :)
Есть ли какой-нибудь смысл в том, чтобы в дочернем классе методы делать виртуальными? То есть, одни и те же методы и в дочернем, и в родительском классе виртуальны. Я нахожу очевидным профитом, что таким образом просто поддерживается возможность "расширения" конечного класса; можно просто увеличивать его функционал когда-нибудь в будущем. Ну а вдруг я уверен, что класс больше развиваться не будет и его никто не захочет наследовать, могут быть какие-нибудь ещё причины делать методы виртуальными в таком случае?
новые примочки для виртуальных ф-ций. В частности ключевое слово final.
В C++11 появились
Да вот только что ознакомился с этими спецификаторами, но пока что не совсем понимаю, зачем может понадобиться использовать спецификатор delete.
Можно без него обойтись : спрятать в private ф-ции, которые под запретом. Но это требует пояснений, а с ним это явно. Кстати, конструктор копии и т.п. часто прячут в приват, если лень его писать, а стандартное копирование нежелательно.
А в какой ситуации может быть нежелательно стандартное копирование? У Страуструпа в примере почему-то нежелательно копирование базового класса (он его запрещает в нём), если вызывается копирование дочернего. Но, чёрт возьми, почему?
А Страуструп наверняка рассматривает более тонкие моменты.
И, таким образом, надо запретить использование присваивания ещё в самом базовом классе, чтобы ни один из дочерних не смог его использовать?..
Не стоит. Лучше оттянуть момент до тех пор, когда появится необходимось запретить. Лучше, конечно, написать нормальный конструктор копии (кстати и оператора присваивания тоже), который будет безопасен по ресурсам. В любом случае, если уж не писать свой, то хотябы запретить копирование в "ресурсовладеющих" классах.
1) спецификатор delete запрещает использование только в том классе, где был написан или и во всех дочерних?
2) как понимать "безопасный по ресурсам" конструктор копирования?
3) когда создаём интерфейсный класс, мы же ещё не знаем, какой из будущем дочерних классов будет "ресурсовладеющим". Стоит ли в этом случае запрещать использование конструкторов копирования?
Я вообще не понимаю, в чём тут может быть проблема. По-моему, конструкторы копирования и присваивания, если они написаны верно, никогда не приведут к каким-то проблемам; ни в родительском классе, ни в дочернем. Они же создают копию ресурса, а не разделяют один общий или лишают кого-то права владения ресурсом, что может привести к ошибкам. Зачем можно хотеть не использовать конструктор копирования и оператор присваивания, если всё и с ними может быть хорошо?
2) Безопасный конструктор должен разорвать связь с ресурсом своего прародителя (если это дискриптор файла, то его видимо просто можно обнулить, если это память в куче, то можно ее выделить в другом месте и скопировать туда содержимое).
3) вродебы я ответил ранее. Кто породит в иерархии ресурсовладеющий класс, тот и должен позаботиться о написании конструктора нового или запретить (поместить в приват )
"Они же создают копию ресурса, а не разделяют один общий". Если так, то нормально. Но стандартный именно создает разделяемый ресурс, а не копию. Мы должны исправить такое поведение в собственном конструкторе копии (и оператора присваивания).
О, то, что нужно. Спасибо, sadovoya.
Вики.
Раз уж речь зашла о ресурсах, то с ними, но уже в другом контексте (захват и освобождение) связана одна мощная идея. Это "запрос ресурсов путем инициализации" (Resource Acquisition Is Initialization, RAII). В прикрепленном файле отрывок из Страуструпа по этой теме. Или еще вот, в
Спасибо. Сейчас ознакомлюсь.