Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Абстрактные виртуальные методы

414
17 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Хай.
Есть два класса контейнера, один из которых является чистым виртуальным, вот они:

 
Код:
class 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;}
};
Знаю, что говнокод, но это чисто для иллюстрации примера. Я бы за такой код вне примеров кастрировал.
И есть одна функция, которая проходит по коллекции и просто выводит в консоль содержимое, вот она:

 
Код:
void use(A &c)
{
    const int sz = c.size();
    for (int i=0; i !=sz; ++i)
        std::cout << c[i] << std::endl;
}
Если вызвать функцию для коллекции, то всё работает. И это меня удивляет.

 
Код:
B a;
a.push_back(4);
a.push_back(5);
a.push_back(6);
use(a);
Что, собственно, меня смущает: в функцию передаётся ссылка на объект виртуального класса, для которого не определён ни один оператор и ни одна функция. Какого же чёрта всё работает? Думаю, что вызываются функции не базового класса, а дочернего, но почему? Какое-то хитрое шаманство с таблицей виртуальных функций? Или что-то ещё?
P.S Страуструп в процессе, не пинайте сильно.
326
17 сентября 2013 года
sadovoya
757 / / 19.11.2005
Обычный динамический полиморфизм. a описана как B, а B имеет право быть и A, вот use и разобралась (по таблице вирт. методов). Тип фактического параметра тут сыграл роль.
414
17 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Что значит "разобралась"? Каков механизм?
И что будет, если будет такая иерархия: A -> B -> C. Классы B и C имеют имплементацию оператора. Имплементацию какого класса тогда выбрала бы use?
Цитата:
Тип фактического параметра тут сыграл роль.


Что означает эта фраза?

326
17 сентября 2013 года
sadovoya
757 / / 19.11.2005
По типу параметра (передаваемого в use), ту реализацию которая имеется для объектов его класса.

Механизм - поиск в таблице.
326
18 сентября 2013 года
sadovoya
757 / / 19.11.2005
Вот нашел один примерчик с комментариями.

Код:
/*
                         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;
}
414
18 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Круто, конечно, но у меня почти такой же пример.
Так как в таблице ищутся нужные функции, если два дочерних класса будут иметь имплементацию функции с одной и той же сигнатурой? Возьмётся тот, который "дочерней" или будет ошибка компиляции?
326
18 сентября 2013 года
sadovoya
757 / / 19.11.2005
Примеры на самом деле эксплуатируют одно понятие -- совместимости по базовому классу дочерних. Но в остальном весьма разные. Я примерчик привел неспроста - там неплохо мощь полиморфизма видна в миниатюре. "Один интерфейс -- множество реализаций".

Нет, ошибки не будет. Наоборот ради таких вещей все и придумано :)

Кстати еще одна важная вещь -- полиморфизм действительно динамический. Работает даже во время исполнения.
414
18 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Окей. Лично я не вижу логики.
Классы А и В описаны выше. Я добавляю класс С, который является наследником В, то есть

 
Код:
class C: public B
{
...
double operator[](int pos) {return _stor[pos];}
...
}
Технически C имеет в предках А. И вот я передаю в функцию use объект класса, С. Вопрос: как функция будет выбирать, реализацию какого класса надо использовать — B или С? Сигнатуры будут одни и те же.
Алсо, профит от полиморфизма я понимаю какой, но хотелось бы знать, как будет работать всё на более низком уровне.
UPD: допущу, что никак не будет понимать и сломается. Поэтому появляется ещё один вопрос: как будет функция работать, если в B и С перегрузку оператора сделать виртуальной?
326
18 сентября 2013 года
sadovoya
757 / / 19.11.2005
Реализацию из C. А общая сигнатура у них у всех еще в классе A задана. Что вас так смущает? Таблица и дает однозначное соответствие, устраняющую путаницу. Я ее на низком уровне себе слабо представляю, но по типу объекта из нее достается нужный вариант ф-ции. Или я вас недопонял..
414
18 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Почему из С, а не из В? Чем же В хуже? :)
Хотя, кажется, начинаю понимать. Потому что созданный объект класса С, а не В, да?
Чёрт возьми, если так. Может, это что-то простое, но для меня выглядит ужасно нелогичным.
326
18 сентября 2013 года
sadovoya
757 / / 19.11.2005
Потому, что "передаю в функцию use объект класса С", а не B. Важен тип фактического параметра (а он сейчас C).

Верно вы поняли.
414
18 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Всё, теперь понял. Спасибо. Осталось только, чтобы во всех компиляторах было так.
326
18 сентября 2013 года
sadovoya
757 / / 19.11.2005
Все компиляторы в этом плане единомышленники. Таков стандарт :)
414
19 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Раз уж завёл тему "виртуальности", то спрошу тут.
Есть ли какой-нибудь смысл в том, чтобы в дочернем классе методы делать виртуальными? То есть, одни и те же методы и в дочернем, и в родительском классе виртуальны. Я нахожу очевидным профитом, что таким образом просто поддерживается возможность "расширения" конечного класса; можно просто увеличивать его функционал когда-нибудь в будущем. Ну а вдруг я уверен, что класс больше развиваться не будет и его никто не захочет наследовать, могут быть какие-нибудь ещё причины делать методы виртуальными в таком случае?
326
19 сентября 2013 года
sadovoya
757 / / 19.11.2005
В C++11 появились новые примочки для виртуальных ф-ций. В частности ключевое слово final.
414
19 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Да вот только что ознакомился с этими спецификаторами, но пока что не совсем понимаю, зачем может понадобиться использовать спецификатор delete.
326
20 сентября 2013 года
sadovoya
757 / / 19.11.2005
Можно без него обойтись : спрятать в private ф-ции, которые под запретом. Но это требует пояснений, а с ним это явно. Кстати, конструктор копии и т.п. часто прячут в приват, если лень его писать, а стандартное копирование нежелательно.
414
20 сентября 2013 года
CassandraDied
763 / / 24.05.2012
А в какой ситуации может быть нежелательно стандартное копирование? У Страуструпа в примере почему-то нежелательно копирование базового класса (он его запрещает в нём), если вызывается копирование дочернего. Но, чёрт возьми, почему?
326
20 сентября 2013 года
sadovoya
757 / / 19.11.2005
Я этот момент у него не помню. Но самый распространенный случай, когда объект владеет ресурсом. Допустим объект открыл файл и сохранил у себя в поле дискриптор. Потом мы создали другой объект, точную копию первого, и закрыли из него файл. Потом первый начал в файл писать (он-же не в курсе, что файл уже закрыт), ну и .. Вы понимаете, что дальше.

А Страуструп наверняка рассматривает более тонкие моменты.
414
20 сентября 2013 года
CassandraDied
763 / / 24.05.2012
И, таким образом, надо запретить использование присваивания ещё в самом базовом классе, чтобы ни один из дочерних не смог его использовать?..
326
20 сентября 2013 года
sadovoya
757 / / 19.11.2005
Не стоит. Лучше оттянуть момент до тех пор, когда появится необходимось запретить. Лучше, конечно, написать нормальный конструктор копии (кстати и оператора присваивания тоже), который будет безопасен по ресурсам. В любом случае, если уж не писать свой, то хотябы запретить копирование в "ресурсовладеющих" классах.
414
20 сентября 2013 года
CassandraDied
763 / / 24.05.2012
У меня три вопроса.
1) спецификатор delete запрещает использование только в том классе, где был написан или и во всех дочерних?
2) как понимать "безопасный по ресурсам" конструктор копирования?
3) когда создаём интерфейсный класс, мы же ещё не знаем, какой из будущем дочерних классов будет "ресурсовладеющим". Стоит ли в этом случае запрещать использование конструкторов копирования?
Я вообще не понимаю, в чём тут может быть проблема. По-моему, конструкторы копирования и присваивания, если они написаны верно, никогда не приведут к каким-то проблемам; ни в родительском классе, ни в дочернем. Они же создают копию ресурса, а не разделяют один общий или лишают кого-то права владения ресурсом, что может привести к ошибкам. Зачем можно хотеть не использовать конструктор копирования и оператор присваивания, если всё и с ними может быть хорошо?
326
20 сентября 2013 года
sadovoya
757 / / 19.11.2005
По 1) -- стандарта 11 не придерживаюсь, не знаю.
2) Безопасный конструктор должен разорвать связь с ресурсом своего прародителя (если это дискриптор файла, то его видимо просто можно обнулить, если это память в куче, то можно ее выделить в другом месте и скопировать туда содержимое).
3) вродебы я ответил ранее. Кто породит в иерархии ресурсовладеющий класс, тот и должен позаботиться о написании конструктора нового или запретить (поместить в приват )
"Они же создают копию ресурса, а не разделяют один общий". Если так, то нормально. Но стандартный именно создает разделяемый ресурс, а не копию. Мы должны исправить такое поведение в собственном конструкторе копии (и оператора присваивания).
414
20 сентября 2013 года
CassandraDied
763 / / 24.05.2012
О, то, что нужно. Спасибо, sadovoya.
326
20 сентября 2013 года
sadovoya
757 / / 19.11.2005
Раз уж речь зашла о ресурсах, то с ними, но уже в другом контексте (захват и освобождение) связана одна мощная идея. Это "запрос ресурсов путем инициализации" (Resource Acquisition Is Initialization, RAII). В прикрепленном файле отрывок из Страуструпа по этой теме. Или еще вот, в Вики.
Прикрепленные файлы:
49 Кб
Загрузок: 1.3K
414
22 сентября 2013 года
CassandraDied
763 / / 24.05.2012
Спасибо. Сейчас ознакомлюсь.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог