Виртуальный деструктор.

Тема в разделе "WASM.BEGINNERS", создана пользователем EvilsInterrupt, 11 сен 2005.

  1. EvilsInterrupt

    EvilsInterrupt Постигающий азы дзена

    Публикаций:
    0
    Регистрация:
    28 окт 2003
    Сообщения:
    2.428
    Адрес:
    Russia
    Здравствуйте.



    Вот взял книгу и в ней написано про виртульный деструктор:



    "Вполне допустимо и общепринято передавать указатель на объект

    производного класса, когда ожидается указатель на объект базо-

    вого класса. Чтоже получается если этот указатель на объект

    производного класса будет удален? Если деструктор будет объяв-

    лен как виртуальный, то все пройдет отлично - будет вызван дест-

    руктор соответствующего производного класса автоматически вызо-

    вет деструктор базового класса, и указанный объект будет удален

    целиком.

    Отсюда следует правило: Если в классе объявлены виртуальные функ-

    ции, то и деструкто должен быть виртуальным!"



    Мне неопонятно:

    1. Когда может возникнуть необходимость передать указатель базо-

    вого и как его может заменить производного класса?

    2. Как все таки вызывается деструктор производного, ведь удаляет

    ся указатель, а не объет!? Т.е. я вижу происхождение утечки па-

    мяти?!



    ЗЫ:

    Спасибо всем за ваше участие в моем изучении тонкостей С++
     
  2. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    EvilsInterrupt

    Хм, ты вдь не первый раз о С++ читаешь. Взять хотя бы Страуструпа: там доходчиво написано, для каких случаев это понадобится.



    1: В случае хранения указателя на абстрактный или базовый класс, а не на конкретный из производных. Очень распространено.



    2: деструктор производного вызывается за счёт его виртуальности: в указателе на базовый класс в Vtbl заносится указатель на деструктор производного (imho, т.к. не смотрел реализацию этого момента). Ну, и размер передаётся не базового, а производного класса в деструктор.
     
  3. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    EvilsInterrupt

    > "Когда может возникнуть необходимость передать указатель базо-

    вого и как его может заменить производного класса?


    > "ведь удаляется указатель, а не объет!?"



    Не путай определения. Есть базовый класс А и есть производный от него класс B. Каждый класс, содержащий виртуальные методы, имеет собственную таблицу виртуальных методов (VMT), в которой по фиксированным смещениям записаны адреса виртуальных методов данного класса. При объявлении наследуемого класса для него создается своя VMT, в которую копируется VMT непосредственного родителя, затем если какие-то виртуальные методы переопределяются, то соответсвующие им адреса в VMT заменяются на новые.

    Оператор New создает экземпляр класса (или другими словами объект в памяти), инициализирует его вызовом конструктора и возвращает указатель на этот экземпляр (объект). При создании экземпляра конструктор записывает ссылку на VMT заданного класса в определенное поле создаваемого экземпляра (например в дельфях, где все классы наследуются от ТObject, ссылка на VMT всегда хранится в первым дворде любого экземпляра любого класса).

    Вызов виртуального метода кодируется так: берется указатель на экземпляр, по фиксированному смещению из него берется ссылка на VMT и по известному смещению из VMT берется указатель на вызываемый метод (у всех наследуемых классов смещение заданного метода в VMT одинаково). Поэтому и не важно, через какой тип указателя был вызван виртуальный метод - реально будет вызван тот к которому приведет ссылка на VMT, а приведет она ес-но к тому типу, с каким создавался объект при вызове New.

    Что происходит, когда мы создаем экземпляр производного класса B и затем передаем его указатель в какую-то процедуру как указатель на родительский класс A ? Ес-но без приведения типов мы уже не можем пользоваться специфическими невиртуальными методами класса B, т.к. их или просто нет в A, или они определены иначе (невиртальные методы вызываются по конкретному адресу как обычные функции). А вот виртуальными методами - пожалуйста, т.к. доступ к ним идет через VMT, ссылка на которую хранится в самом экземпляре. Поэтому, грубо говоря, процедуре лишь кажется что она работает с классом A, а на саммом деле через VMT вызываются виртуальные методы класса B (т.е. истинного типа экземпляра B). Поэтому если процедура вызывает Delete для удаления экземпляра, то автоматом будет вызван соответсвующий ему виртуальный деструктор, независимо от типа указателя, переданного в Delete.

    Ну а вызвать собственные невиртуальные методы класса B, переданного как указатель на A, можно через обычное приведение типов.



    Используется передача указателей на базовые классы довольно часто - когда процедура или контейнер оперирует лишь ограниченным набором базовых методов и частные тонкости реализации наследников знать не нужно. Это позволяет передавать и хранить указатели на разнородные объекты одной иерархии. Например, если от A наследуется три разных класса B,C и D то можно их всех передавать как A, хранить в одном списке и удалять все скопом в простом цикле. В частности, в VCL любая форма хранит список всех разнородных контролов (TButton, TEdit и т.д.) в виде ссылок на базовый класс TControl.
     
  4. EvilsInterrupt

    EvilsInterrupt Постигающий азы дзена

    Публикаций:
    0
    Регистрация:
    28 окт 2003
    Сообщения:
    2.428
    Адрес:
    Russia
    Вот это ответ! Рулез, спасибо!
     
  5. EvilsInterrupt

    EvilsInterrupt Постигающий азы дзена

    Публикаций:
    0
    Регистрация:
    28 окт 2003
    Сообщения:
    2.428
    Адрес:
    Russia
    стоко всего начитался, стоко всего усвоилось, но последнее,

    почему, class A{}; class B public A{};

    A * pB = new B // почему это не вызывает у компилятора смущения?

    Ведь B хоть и производный от А, но тем не менее является совсем другим типом!
     
  6. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Дык это же одна из главных фишек ООП - возможность создавать абстрактные базовые классы, которые обеспечивают полную или достаточную функциональность всего класса. Но экземпляры абстрактного класса создать нельзя, это лишь набор интерфейсных функций и свойств. Поэтому конкретная реализация хранения и выдачи данных осуществляется в потомках и может быть разной. Такой подход позволяет работать с указателями на абстрактный класс А, не заботясь о том какой реальный производный класс будет скрываться за этим указателем. Например, в VCL широко используется абстракный класс TStrings (список строк), за которым на самом деле скрывается множество различных реализаций (как public, так и private). Например, TStringList сам хранит массив указателей на строки, а вот приватные реализации TListBoxStrings, TComboBoxStrings и TMemoStrings это просто интерфейсные адаптеры для работы с соответсвующими виндовыми контролами и все методы у них реализованы на SendMessage своему контролу.

    Поэтому в ООП указатель на родительский класс A всегда совместим по присваиванию с указателями на любых потомков B (но не наоборот). Поэтому твои слова нужно перефразировать с точностью до наоборот: хоть B и другой тип, но он является производным от А, а значит он содержит все свойства и методы класса А и значит с ним можно обращаться как с классом А.