Здравствуйте. Вот взял книгу и в ней написано про виртульный деструктор: "Вполне допустимо и общепринято передавать указатель на объект производного класса, когда ожидается указатель на объект базо- вого класса. Чтоже получается если этот указатель на объект производного класса будет удален? Если деструктор будет объяв- лен как виртуальный, то все пройдет отлично - будет вызван дест- руктор соответствующего производного класса автоматически вызо- вет деструктор базового класса, и указанный объект будет удален целиком. Отсюда следует правило: Если в классе объявлены виртуальные функ- ции, то и деструкто должен быть виртуальным!" Мне неопонятно: 1. Когда может возникнуть необходимость передать указатель базо- вого и как его может заменить производного класса? 2. Как все таки вызывается деструктор производного, ведь удаляет ся указатель, а не объет!? Т.е. я вижу происхождение утечки па- мяти?! ЗЫ: Спасибо всем за ваше участие в моем изучении тонкостей С++
EvilsInterrupt Хм, ты вдь не первый раз о С++ читаешь. Взять хотя бы Страуструпа: там доходчиво написано, для каких случаев это понадобится. 1: В случае хранения указателя на абстрактный или базовый класс, а не на конкретный из производных. Очень распространено. 2: деструктор производного вызывается за счёт его виртуальности: в указателе на базовый класс в Vtbl заносится указатель на деструктор производного (imho, т.к. не смотрел реализацию этого момента). Ну, и размер передаётся не базового, а производного класса в деструктор.
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.
стоко всего начитался, стоко всего усвоилось, но последнее, почему, class A{}; class B public A{}; A * pB = new B // почему это не вызывает у компилятора смущения? Ведь B хоть и производный от А, но тем не менее является совсем другим типом!
Дык это же одна из главных фишек ООП - возможность создавать абстрактные базовые классы, которые обеспечивают полную или достаточную функциональность всего класса. Но экземпляры абстрактного класса создать нельзя, это лишь набор интерфейсных функций и свойств. Поэтому конкретная реализация хранения и выдачи данных осуществляется в потомках и может быть разной. Такой подход позволяет работать с указателями на абстрактный класс А, не заботясь о том какой реальный производный класс будет скрываться за этим указателем. Например, в VCL широко используется абстракный класс TStrings (список строк), за которым на самом деле скрывается множество различных реализаций (как public, так и private). Например, TStringList сам хранит массив указателей на строки, а вот приватные реализации TListBoxStrings, TComboBoxStrings и TMemoStrings это просто интерфейсные адаптеры для работы с соответсвующими виндовыми контролами и все методы у них реализованы на SendMessage своему контролу. Поэтому в ООП указатель на родительский класс A всегда совместим по присваиванию с указателями на любых потомков B (но не наоборот). Поэтому твои слова нужно перефразировать с точностью до наоборот: хоть B и другой тип, но он является производным от А, а значит он содержит все свойства и методы класса А и значит с ним можно обращаться как с классом А.